gimp/plug-ins/common/bump-map.c

1445 lines
45 KiB
C

/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* Bump map plug-in --- emboss an image by using another image as a bump map
* Copyright (C) 1997 Federico Mena Quintero <federico@nuclecu.unam.mx>
* Copyright (C) 1997-2000 Jens Lautenbacher <jtl@gimp.org>
* Copyright (C) 2000 Sven Neumann <sven@gimp.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* 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.
*/
#include "config.h"
#include <string.h>
#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>
#include "libgimp/stdplugins-intl.h"
/***** Magic numbers *****/
#define PLUG_IN_PROC "plug-in-bump-map"
#define PLUG_IN_TILED_PROC "plug-in-bump-map-tiled"
#define PLUG_IN_BINARY "bumpmap"
#define PLUG_IN_VERSION "April 2000, 3.0-pre1-ac2"
#define SCALE_WIDTH 100
/***** Types *****/
enum
{
LINEAR = 0,
SPHERICAL,
SINUSOIDAL
};
enum
{
DRAG_NONE = 0,
DRAG_BUMPMAP
};
typedef struct
{
gint32 bumpmap_id;
gdouble azimuth;
gdouble elevation;
gint depth;
gint xofs;
gint yofs;
gint waterlevel;
gint ambient;
gboolean compensate;
gboolean invert;
gint type;
gboolean tiled;
} bumpmap_vals_t;
typedef struct
{
gint lx, ly; /* X and Y components of light vector */
gint nz2, nzlz; /* nz^2, nz*lz */
gint background; /* Shade for vertical normals */
gdouble compensation; /* Background compensation */
guchar lut[256]; /* Look-up table for modes */
} bumpmap_params_t;
typedef struct
{
gint mouse_x;
gint mouse_y;
gint drag_mode;
GtkObject *offset_adj_x;
GtkObject *offset_adj_y;
guchar **src_rows;
guchar **bm_rows;
GimpDrawable *bm_drawable;
gint bm_width;
gint bm_height;
gint bm_bpp;
gboolean bm_has_alpha;
GimpPixelRgn src_rgn;
GimpPixelRgn bm_rgn;
bumpmap_params_t params;
} bumpmap_interface_t;
/***** Prototypes *****/
static void query (void);
static void run (const gchar *name,
gint nparams,
const GimpParam *param,
gint *nreturn_vals,
GimpParam **return_vals);
static void bumpmap (void);
static void bumpmap_init_params (bumpmap_params_t *params);
static void bumpmap_row (const guchar *src_row,
guchar *dest_row,
gint width,
gint bpp,
gboolean has_alpha,
const guchar *bm_row1,
const guchar *bm_row2,
const guchar *bm_row3,
gint bm_width,
gint bm_xofs,
gboolean tiled,
gboolean row_in_bumpmap,
bumpmap_params_t *params);
static void bumpmap_convert_row (guchar *row,
gint width,
gint bpp,
gboolean has_alpha,
const guchar *lut);
static gboolean bumpmap_dialog (void);
static void dialog_new_bumpmap (gboolean init_offsets);
static void dialog_update_preview (GimpPreview *preview);
static gboolean dialog_preview_events (GtkWidget *area,
GdkEvent *event,
GimpPreview *preview);
static void dialog_get_rows (GimpPixelRgn *pr,
guchar **rows,
gint x,
gint y,
gint width,
gint height);
static void dialog_fill_src_rows (gint start,
gint how_many,
gint yofs);
static void dialog_fill_bumpmap_rows (gint start,
gint how_many,
gint yofs);
static gint dialog_constrain (gint32 image_id,
gint32 drawable_id,
gpointer data);
static void dialog_bumpmap_callback (GtkWidget *widget,
GimpPreview *preview);
static void dialog_maptype_callback (GtkWidget *widget,
GimpPreview *preview);
/***** Variables *****/
const GimpPlugInInfo PLUG_IN_INFO =
{
NULL, /* init_proc */
NULL, /* quit_proc */
query, /* query_proc */
run /* run_proc */
};
static bumpmap_vals_t bmvals =
{
-1, /* bumpmap_id */
135.0, /* azimuth */
45.0, /* elevation */
3, /* depth */
0, /* xofs */
0, /* yofs */
0, /* waterlevel */
0, /* ambient */
TRUE, /* compensate */
FALSE, /* invert */
LINEAR, /* type */
FALSE /* tiled */
};
static bumpmap_interface_t bmint =
{
0, /* mouse_x */
0, /* mouse_y */
DRAG_NONE, /* drag_mode */
NULL, /* offset_adj_x */
NULL, /* offset_adj_y */
NULL, /* src_rows */
NULL, /* bm_rows */
NULL, /* bm_drawable */
0, /* bm_width */
0, /* bm_height */
0, /* bm_bpp */
FALSE, /* bm_has_alpha */
{ 0, }, /* src_rgn */
{ 0, }, /* bm_rgn */
{ 0, } /* params */
};
static GimpDrawable *drawable = NULL;
static gint sel_x1, sel_y1;
static gint sel_x2, sel_y2;
static gint sel_width, sel_height;
static gint img_bpp;
static gboolean img_has_alpha;
/***** Macros *****/
#define MOD(x, y) \
((x) < 0 ? ((y) - 1 - ((y) - 1 - (x)) % (y)) : (x) % (y))
/***** Functions *****/
MAIN ()
static void
query (void)
{
static const GimpParamDef args[] =
{
{ GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
{ GIMP_PDB_IMAGE, "image", "Input image" },
{ GIMP_PDB_DRAWABLE, "drawable", "Input drawable" },
{ GIMP_PDB_DRAWABLE, "bumpmap", "Bump map drawable" },
{ GIMP_PDB_FLOAT, "azimuth", "Azimuth" },
{ GIMP_PDB_FLOAT, "elevation", "Elevation" },
{ GIMP_PDB_INT32, "depth", "Depth" },
{ GIMP_PDB_INT32, "xofs", "X offset" },
{ GIMP_PDB_INT32, "yofs", "Y offset" },
{ GIMP_PDB_INT32, "waterlevel", "Level that full transparency "
"should represent" },
{ GIMP_PDB_INT32, "ambient", "Ambient lighting factor" },
{ GIMP_PDB_INT32, "compensate", "Compensate for darkening { TRUE, FALSE }" },
{ GIMP_PDB_INT32, "invert", "Invert bumpmap { TRUE, FALSE }" },
{ GIMP_PDB_INT32, "type", "Type of map { LINEAR (0), "
"SPHERICAL (1), SINUSOIDAL (2) }" }
};
gimp_install_procedure (PLUG_IN_PROC,
N_("Create an embossing effect using a bump map"),
"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.",
"Federico Mena Quintero, Jens Lautenbacher & Sven Neumann",
"Federico Mena Quintero, Jens Lautenbacher & Sven Neumann",
PLUG_IN_VERSION,
N_("_Bump Map..."),
"RGB*, GRAY*",
GIMP_PLUGIN,
G_N_ELEMENTS (args), 0,
args, NULL);
gimp_plugin_menu_register (PLUG_IN_PROC, "<Image>/Filters/Map");
gimp_install_procedure (PLUG_IN_TILED_PROC,
"Create an embossing effect using a tiled image "
"as a bump map",
"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 tiled and applied as a "
"bump map to another image and produces a nice "
"embossing effect.",
"Federico Mena Quintero, Jens Lautenbacher & Sven Neumann",
"Federico Mena Quintero, Jens Lautenbacher & Sven Neumann",
PLUG_IN_VERSION,
NULL,
"RGB*, GRAY*",
GIMP_PLUGIN,
G_N_ELEMENTS (args), 0,
args, NULL);
}
static void
run (const gchar *name,
gint nparams,
const GimpParam *param,
gint *nreturn_vals,
GimpParam **return_vals)
{
static GimpParam values[1];
GimpRunMode run_mode;
GimpPDBStatusType status;
INIT_I18N ();
status = GIMP_PDB_SUCCESS;
run_mode = param[0].data.d_int32;
values[0].type = GIMP_PDB_STATUS;
values[0].data.d_status = status;
*nreturn_vals = 1;
*return_vals = values;
/* Get drawable information */
drawable = gimp_drawable_get (param[2].data.d_drawable);
gimp_drawable_mask_bounds (drawable->drawable_id,
&sel_x1, &sel_y1, &sel_x2, &sel_y2);
sel_width = sel_x2 - sel_x1;
sel_height = sel_y2 - sel_y1;
img_bpp = gimp_drawable_bpp (drawable->drawable_id);
img_has_alpha = gimp_drawable_has_alpha (drawable->drawable_id);
/* See how we will run */
switch (run_mode)
{
case GIMP_RUN_INTERACTIVE:
/* Possibly retrieve data */
gimp_get_data (name, &bmvals);
/* Get information from the dialog */
if (!bumpmap_dialog ())
return;
break;
case GIMP_RUN_NONINTERACTIVE:
/* Make sure all the arguments are present */
if (nparams != 14)
{
status = GIMP_PDB_CALLING_ERROR;
}
else
{
bmvals.bumpmap_id = param[3].data.d_drawable;
bmvals.azimuth = param[4].data.d_float;
bmvals.elevation = param[5].data.d_float;
bmvals.depth = param[6].data.d_int32;
bmvals.xofs = param[7].data.d_int32;
bmvals.yofs = param[8].data.d_int32;
bmvals.waterlevel = param[9].data.d_int32;
bmvals.ambient = param[10].data.d_int32;
bmvals.compensate = param[11].data.d_int32;
bmvals.invert = param[12].data.d_int32;
bmvals.type = param[13].data.d_int32;
bmvals.tiled = strcmp (name, PLUG_IN_TILED_PROC) == 0;
}
break;
case GIMP_RUN_WITH_LAST_VALS:
/* Possibly retrieve data */
gimp_get_data (name, &bmvals);
break;
default:
break;
}
/* Bumpmap the image */
if (status == GIMP_PDB_SUCCESS)
{
if ((gimp_drawable_is_rgb(drawable->drawable_id) ||
gimp_drawable_is_gray(drawable->drawable_id)))
{
/* Run! */
bumpmap ();
/* If run mode is interactive, flush displays */
if (run_mode != GIMP_RUN_NONINTERACTIVE)
gimp_displays_flush ();
/* Store data */
if (run_mode == GIMP_RUN_INTERACTIVE)
gimp_set_data (name, &bmvals, sizeof (bumpmap_vals_t));
}
}
else
status = GIMP_PDB_EXECUTION_ERROR;
values[0].data.d_status = status;
gimp_drawable_detach (drawable);
}
static void
bumpmap (void)
{
bumpmap_params_t params;
GimpDrawable *bm_drawable;
GimpPixelRgn src_rgn, dest_rgn, bm_rgn;
gint bm_width, bm_height, bm_bpp, bm_has_alpha;
gint yofs1, yofs2, yofs3;
gboolean row_in_bumpmap;
guchar *bm_row1, *bm_row2, *bm_row3, *bm_tmprow;
guchar *src_row, *dest_row;
gint y;
gint progress;
gint drawable_tiles_per_row, bm_tiles_per_row;
gimp_progress_init (_("Bump-mapping"));
/* Get the bumpmap drawable */
if (bmvals.bumpmap_id != -1)
bm_drawable = gimp_drawable_get (bmvals.bumpmap_id);
else
bm_drawable = drawable;
if (!bm_drawable)
return;
/* Get image information */
bm_width = gimp_drawable_width (bm_drawable->drawable_id);
bm_height = gimp_drawable_height (bm_drawable->drawable_id);
bm_bpp = gimp_drawable_bpp (bm_drawable->drawable_id);
bm_has_alpha = gimp_drawable_has_alpha (bm_drawable->drawable_id);
/* Set the tile cache size */
/* Compute number of tiles needed for one row of the drawable */
drawable_tiles_per_row =
1
+ (sel_x2 + gimp_tile_width () - 1) / gimp_tile_width ()
- sel_x1 / gimp_tile_width ();
/* Compute number of tiles needed for one row of the bitmap */
bm_tiles_per_row = (bm_width + gimp_tile_width () - 1) / gimp_tile_width ();
/* Cache one row of source, destination and bitmap */
gimp_tile_cache_ntiles (bm_tiles_per_row + 2 * drawable_tiles_per_row);
/* Initialize offsets */
if (bmvals.tiled)
{
yofs2 = MOD (bmvals.yofs + sel_y1, bm_height);
yofs1 = MOD (yofs2 - 1, bm_height);
yofs3 = MOD (yofs2 + 1, bm_height);
}
else
{
yofs2 = CLAMP (bmvals.yofs + sel_y1, 0, bm_height - 1);
yofs1 = yofs2;
yofs3 = CLAMP (yofs2 + 1, 0, bm_height - 1);
}
/* Initialize row buffers */
bm_row1 = g_new (guchar, bm_width * bm_bpp);
bm_row2 = g_new (guchar, bm_width * bm_bpp);
bm_row3 = g_new (guchar, bm_width * bm_bpp);
src_row = g_new (guchar, sel_width * img_bpp);
dest_row = g_new (guchar, sel_width * img_bpp);
/* Initialize pixel regions */
gimp_pixel_rgn_init (&src_rgn, drawable,
sel_x1, sel_y1, sel_width, sel_height, FALSE, FALSE);
gimp_pixel_rgn_init (&dest_rgn, drawable,
sel_x1, sel_y1, sel_width, sel_height, TRUE, TRUE);
gimp_pixel_rgn_init (&bm_rgn, bm_drawable,
0, 0, bm_width, bm_height, FALSE, FALSE);
/* Bumpmap */
bumpmap_init_params (&params);
gimp_pixel_rgn_get_row (&bm_rgn, bm_row1, 0, yofs1, bm_width);
gimp_pixel_rgn_get_row (&bm_rgn, bm_row2, 0, yofs2, bm_width);
gimp_pixel_rgn_get_row (&bm_rgn, bm_row3, 0, yofs3, bm_width);
bumpmap_convert_row (bm_row1, bm_width, bm_bpp, bm_has_alpha, params.lut);
bumpmap_convert_row (bm_row2, bm_width, bm_bpp, bm_has_alpha, params.lut);
bumpmap_convert_row (bm_row3, bm_width, bm_bpp, bm_has_alpha, params.lut);
for (y = sel_y1, progress = 0; y < sel_y2; y++, progress++)
{
row_in_bumpmap = (y >= - bmvals.yofs && y < - bmvals.yofs + bm_height);
gimp_pixel_rgn_get_row (&src_rgn, src_row, sel_x1, y, sel_width);
bumpmap_row (src_row, dest_row, sel_width, img_bpp, img_has_alpha,
bm_row1, bm_row2, bm_row3, bm_width, bmvals.xofs,
bmvals.tiled,
row_in_bumpmap,
&params);
gimp_pixel_rgn_set_row (&dest_rgn, dest_row, sel_x1, y, sel_width);
/* Next line */
if (bmvals.tiled || row_in_bumpmap)
{
bm_tmprow = bm_row1;
bm_row1 = bm_row2;
bm_row2 = bm_row3;
bm_row3 = bm_tmprow;
if (++yofs2 == bm_height)
yofs2 = 0;
if (bmvals.tiled)
yofs3 = MOD (yofs2 + 1, bm_height);
else
yofs3 = CLAMP (yofs2 + 1, 0, bm_height - 1);
gimp_pixel_rgn_get_row (&bm_rgn, bm_row3, 0, yofs3, bm_width);
bumpmap_convert_row (bm_row3, bm_width, bm_bpp, bm_has_alpha,
params.lut);
}
if ((progress % 16) == 0)
gimp_progress_update ((gdouble) progress / sel_height);
}
/* Done */
g_free (bm_row1);
g_free (bm_row2);
g_free (bm_row3);
g_free (src_row);
g_free (dest_row);
if (bm_drawable != drawable)
gimp_drawable_detach (bm_drawable);
gimp_drawable_flush (drawable);
gimp_drawable_merge_shadow (drawable->drawable_id, TRUE);
gimp_drawable_update (drawable->drawable_id,
sel_x1, sel_y1, sel_width, sel_height);
}
static void
bumpmap_init_params (bumpmap_params_t *params)
{
/* Convert to radians */
const gdouble azimuth = G_PI * bmvals.azimuth / 180.0;
const gdouble elevation = G_PI * bmvals.elevation / 180.0;
gint lz, nz;
gint i;
/* Calculate the light vector */
params->lx = cos (azimuth) * cos (elevation) * 255.0;
params->ly = sin (azimuth) * cos (elevation) * 255.0;
lz = sin (elevation) * 255.0;
/* Calculate constant Z component of surface normal */
/* (depth may be 0 if non-interactive) */
nz = (6 * 255) / MAX (bmvals.depth, 1);
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 < 256; i++)
{
gdouble n;
switch (bmvals.type)
{
case SPHERICAL:
n = i / 255.0 - 1.0;
params->lut[i] = (int) (255.0 * sqrt(1.0 - n * n) + 0.5);
break;
case SINUSOIDAL:
n = i / 255.0;
params->lut[i] = (int) (255.0 *
(sin((-G_PI / 2.0) + G_PI * n) + 1.0) /
2.0 + 0.5);
break;
case LINEAR:
default:
params->lut[i] = i;
}
if (bmvals.invert)
params->lut[i] = 255 - params->lut[i];
}
}
static void
bumpmap_row (const guchar *src,
guchar *dest,
gint width,
gint bpp,
gboolean has_alpha,
const guchar *bm_row1,
const guchar *bm_row2,
const guchar *bm_row3,
gint bm_width,
gint bm_xofs,
gboolean tiled,
gboolean row_in_bumpmap,
bumpmap_params_t *params)
{
gint xofs1, xofs2;
gint x, k;
gint pbpp;
gint result;
gint tmp;
if (has_alpha)
pbpp = bpp - 1;
else
pbpp = bpp;
tmp = bm_xofs + sel_x1;
xofs2 = MOD (tmp, bm_width);
for (x = 0; x < width; x++)
{
gint xofs3;
gint shade;
gint nx, ny;
/* Calculate surface normal from bump map */
if (tiled || (row_in_bumpmap &&
x >= - tmp && x < - tmp + bm_width))
{
if (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;
}
/* Shade */
if ((nx == 0) && (ny == 0))
{
shade = params->background;
}
else
{
gint ndotl = nx * params->lx + ny * params->ly + params->nzlz;
if (ndotl < 0)
{
shade = params->compensation * bmvals.ambient;
}
else
{
shade = ndotl / sqrt (nx * nx + ny * ny + params->nz2);
shade = shade + MAX(0, (255 * params->compensation - shade)) *
bmvals.ambient / 255;
}
}
/* Paint */
if (bmvals.compensate)
{
for (k = pbpp; k; k--)
{
result = (*src++ * shade) / (params->compensation * 255);
*dest++ = MIN(255, result);
}
}
else
{
for (k = pbpp; k; k--)
*dest++ = *src++ * shade / 255;
}
if (has_alpha)
*dest++ = *src++;
/* Next pixel */
if (++xofs2 == bm_width)
xofs2 = 0;
}
}
static void
bumpmap_convert_row (guchar *row,
gint width,
gint bpp,
gboolean has_alpha,
const guchar *lut)
{
guchar *p = row;
has_alpha = has_alpha ? 1 : 0;
if (bpp >= 3)
for (; width; width--)
{
if (has_alpha)
*p++ = lut[(gint) (bmvals.waterlevel +
(((gint) (GIMP_RGB_LUMINANCE (row[0],
row[1],
row[2]) + 0.5) -
bmvals.waterlevel) * row[3]) / 255.0)];
else
*p++ = lut[(gint) (GIMP_RGB_LUMINANCE (row[0],
row[1],
row[2]) + 0.5)];
row += 3 + has_alpha;
}
else
for (; width; width--)
{
if (has_alpha)
*p++ = lut[bmvals.waterlevel +
((row[0] - bmvals.waterlevel) * row[1]) / 255];
else
*p++ = lut[*row];
row += 1 + has_alpha;
}
}
static gboolean
bumpmap_dialog (void)
{
GtkWidget *dialog;
GtkWidget *paned;
GtkWidget *hbox;
GtkWidget *vbox;
GtkWidget *preview;
GtkWidget *table;
GtkWidget *combo;
GtkWidget *button;
GtkObject *adj;
gboolean run;
gint row = 0;
gimp_ui_init (PLUG_IN_BINARY, TRUE);
dialog = gimp_dialog_new (_("Bump Map"), PLUG_IN_BINARY,
NULL, 0,
gimp_standard_help_func, PLUG_IN_PROC,
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
GTK_STOCK_OK, GTK_RESPONSE_OK,
NULL);
gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
GTK_RESPONSE_OK,
GTK_RESPONSE_CANCEL,
-1);
gimp_window_set_transient (GTK_WINDOW (dialog));
paned = gtk_hpaned_new ();
gtk_container_set_border_width (GTK_CONTAINER (paned), 12);
gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
paned);
gtk_widget_show (paned);
hbox = gtk_hbox_new (FALSE, 0);
gtk_paned_pack1 (GTK_PANED (paned), hbox, TRUE, FALSE);
gtk_widget_show (hbox);
vbox = gtk_vbox_new (FALSE, 0);
gtk_container_set_border_width (GTK_CONTAINER (vbox), 4);
gtk_box_pack_end (GTK_BOX (hbox), vbox, FALSE, FALSE, 0);
gtk_widget_show (vbox);
preview = gimp_drawable_preview_new (drawable, NULL);
gtk_container_add (GTK_CONTAINER (hbox), preview);
gtk_widget_show (preview);
g_signal_connect (preview, "invalidated",
G_CALLBACK (dialog_update_preview),
NULL);
g_signal_connect (GIMP_PREVIEW (preview)->area, "event",
G_CALLBACK (dialog_preview_events), preview);
hbox = gtk_hbox_new (FALSE, 0);
gtk_paned_pack2 (GTK_PANED (paned), hbox, FALSE, FALSE);
gtk_widget_show (hbox);
vbox = gtk_vbox_new (FALSE, 0);
gtk_container_set_border_width (GTK_CONTAINER (vbox), 4);
gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0);
gtk_widget_show (vbox);
vbox = gtk_vbox_new (FALSE, 0);
gtk_container_add (GTK_CONTAINER (hbox), vbox);
gtk_widget_show (vbox);
table = gtk_table_new (12, 3, FALSE);
gtk_table_set_col_spacings (GTK_TABLE (table), 6);
gtk_table_set_row_spacings (GTK_TABLE (table), 6);
gtk_box_pack_start (GTK_BOX (vbox), table, FALSE, FALSE, 0);
gtk_widget_show (table);
/* Bump map menu */
combo = gimp_drawable_combo_box_new (dialog_constrain, NULL);
gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), bmvals.bumpmap_id,
G_CALLBACK (dialog_bumpmap_callback),
preview);
gimp_table_attach_aligned (GTK_TABLE (table), 0, row++,
_("_Bump map:"), 0.0, 0.5, combo, 2, FALSE);
/* Map type menu */
combo = gimp_int_combo_box_new (_("Linear"), LINEAR,
_("Spherical"), SPHERICAL,
_("Sinusoidal"), SINUSOIDAL,
NULL);
gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), bmvals.type,
G_CALLBACK (dialog_maptype_callback),
preview);
gimp_table_attach_aligned (GTK_TABLE (table), 0, row,
_("_Map type:"), 0.0, 0.5, combo, 2, FALSE);
gtk_table_set_row_spacing (GTK_TABLE (table), row++, 12);
/* Compensate darkening */
button = gtk_check_button_new_with_mnemonic (_("Co_mpensate for darkening"));
gtk_table_attach_defaults (GTK_TABLE (table), button, 0, 3, row, row + 1);
gtk_widget_show (button);
row++;
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), bmvals.compensate);
g_signal_connect (button, "toggled",
G_CALLBACK (gimp_toggle_button_update),
&bmvals.compensate);
g_signal_connect_swapped (button, "toggled",
G_CALLBACK (gimp_preview_invalidate),
preview);
/* Invert bumpmap */
button = gtk_check_button_new_with_mnemonic (_("I_nvert bumpmap"));
gtk_table_attach_defaults (GTK_TABLE (table), button, 0, 3, row, row + 1);
gtk_widget_show (button);
row++;
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), bmvals.invert);
g_signal_connect (button, "toggled",
G_CALLBACK (gimp_toggle_button_update),
&bmvals.invert);
g_signal_connect_swapped (button, "toggled",
G_CALLBACK (gimp_preview_invalidate),
preview);
/* Tile bumpmap */
button = gtk_check_button_new_with_mnemonic (_("_Tile bumpmap"));
gtk_table_attach_defaults (GTK_TABLE (table), button, 0, 3, row, row + 1);
gtk_widget_show (button);
gtk_table_set_row_spacing (GTK_TABLE (table), row++, 12);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), bmvals.tiled);
g_signal_connect (button, "toggled",
G_CALLBACK (gimp_toggle_button_update),
&bmvals.tiled);
g_signal_connect_swapped (button, "toggled",
G_CALLBACK (gimp_preview_invalidate),
preview);
adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++,
_("_Azimuth:"), SCALE_WIDTH, 6,
bmvals.azimuth, 0.0, 360.0, 1.0, 15.0, 2,
TRUE, 0, 0,
NULL, NULL);
g_signal_connect (adj, "value-changed",
G_CALLBACK (gimp_double_adjustment_update),
&bmvals.azimuth);
g_signal_connect_swapped (adj, "value-changed",
G_CALLBACK (gimp_preview_invalidate),
preview);
adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++,
_("_Elevation:"), SCALE_WIDTH, 6,
bmvals.elevation, 0.5, 90.0, 1.0, 5.0, 2,
TRUE, 0, 0,
NULL, NULL);
g_signal_connect (adj, "value-changed",
G_CALLBACK (gimp_double_adjustment_update),
&bmvals.elevation);
g_signal_connect_swapped (adj, "value-changed",
G_CALLBACK (gimp_preview_invalidate),
preview);
adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row,
_("_Depth:"), SCALE_WIDTH, 6,
bmvals.depth, 1.0, 65.0, 1.0, 5.0, 0,
TRUE, 0, 0,
NULL, NULL);
g_signal_connect (adj, "value-changed",
G_CALLBACK (gimp_int_adjustment_update),
&bmvals.depth);
g_signal_connect_swapped (adj, "value-changed",
G_CALLBACK (gimp_preview_invalidate),
preview);
gtk_table_set_row_spacing (GTK_TABLE (table), row++, 12);
bmint.offset_adj_x = adj =
gimp_scale_entry_new (GTK_TABLE (table), 0, row++,
_("_X offset:"), SCALE_WIDTH, 6,
bmvals.xofs, -1000.0, 1001.0, 1.0, 10.0, 0,
TRUE, 0, 0,
_("The offset can be adjusted by dragging the "
"preview using the middle mouse button."), NULL);
g_signal_connect (adj, "value-changed",
G_CALLBACK (gimp_int_adjustment_update),
&bmvals.xofs);
g_signal_connect_swapped (adj, "value-changed",
G_CALLBACK (gimp_preview_invalidate),
preview);
bmint.offset_adj_y = adj =
gimp_scale_entry_new (GTK_TABLE (table), 0, row,
_("_Y offset:"), SCALE_WIDTH, 6,
bmvals.yofs, -1000.0, 1001.0, 1.0, 10.0, 0,
TRUE, 0, 0,
_("The offset can be adjusted by dragging the "
"preview using the middle mouse button."), NULL);
g_signal_connect (adj, "value-changed",
G_CALLBACK (gimp_int_adjustment_update),
&bmvals.yofs);
g_signal_connect_swapped (adj, "value-changed",
G_CALLBACK (gimp_preview_invalidate),
preview);
gtk_table_set_row_spacing (GTK_TABLE (table), row++, 12);
adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++,
_("_Waterlevel:"), SCALE_WIDTH, 6,
bmvals.waterlevel, 0.0, 255.0, 1.0, 8.0, 0,
TRUE, 0, 0,
NULL, NULL);
g_signal_connect (adj, "value-changed",
G_CALLBACK (gimp_int_adjustment_update),
&bmvals.waterlevel);
g_signal_connect_swapped (adj, "value-changed",
G_CALLBACK (gimp_preview_invalidate),
preview);
adj = gimp_scale_entry_new (GTK_TABLE (table), 0, row++,
_("A_mbient:"), SCALE_WIDTH, 6,
bmvals.ambient, 0.0, 255.0, 1.0, 8.0, 0,
TRUE, 0, 0,
NULL, NULL);
g_signal_connect (adj, "value-changed",
G_CALLBACK (gimp_int_adjustment_update),
&bmvals.ambient);
g_signal_connect_swapped (adj, "value-changed",
G_CALLBACK (gimp_preview_invalidate),
preview);
/* Initialise drawable
* (don't initialise offsets if bumpmap_id is already known)
*/
if (bmvals.bumpmap_id == -1)
dialog_new_bumpmap (TRUE);
else
dialog_new_bumpmap (FALSE);
/* Done */
gtk_widget_show (dialog);
run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
gtk_widget_destroy (dialog);
if (bmint.bm_drawable != drawable)
gimp_drawable_detach (bmint.bm_drawable);
return run;
}
static gboolean
dialog_preview_events (GtkWidget *area,
GdkEvent *event,
GimpPreview *preview)
{
switch (event->type)
{
case GDK_BUTTON_PRESS:
{
GdkEventButton *bevent = (GdkEventButton *) event;
switch (bevent->button)
{
case 2:
bmint.drag_mode = DRAG_BUMPMAP;
break;
default:
return FALSE;
}
bmint.mouse_x = bevent->x;
bmint.mouse_y = bevent->y;
gtk_grab_add (area);
return TRUE;
}
break;
case GDK_BUTTON_RELEASE:
if (bmint.drag_mode != DRAG_NONE)
{
gtk_grab_remove (area);
bmint.drag_mode = DRAG_NONE;
gimp_preview_invalidate (preview);
return TRUE;
}
break;
case GDK_MOTION_NOTIFY:
{
GdkEventMotion *mevent = (GdkEventMotion *) event;
gint dx = mevent->x - bmint.mouse_x;
gint dy = mevent->y - bmint.mouse_y;
bmint.mouse_x = mevent->x;
bmint.mouse_y = mevent->y;
gdk_event_request_motions (mevent);
if ((dx == 0) && (dy == 0))
break;
switch (bmint.drag_mode)
{
case DRAG_BUMPMAP:
bmvals.xofs = CLAMP (bmvals.xofs - dx, -1000, 1000);
g_signal_handlers_block_by_func (bmint.offset_adj_x,
gimp_int_adjustment_update,
&bmvals.xofs);
gtk_adjustment_set_value (GTK_ADJUSTMENT (bmint.offset_adj_x),
bmvals.xofs);
g_signal_handlers_unblock_by_func (bmint.offset_adj_x,
gimp_int_adjustment_update,
&bmvals.xofs);
bmvals.yofs = CLAMP (bmvals.yofs - dy, -1000, 1000);
g_signal_handlers_block_by_func (bmint.offset_adj_y,
gimp_int_adjustment_update,
&bmvals.yofs);
gtk_adjustment_set_value (GTK_ADJUSTMENT (bmint.offset_adj_y),
bmvals.yofs);
g_signal_handlers_unblock_by_func (bmint.offset_adj_y,
gimp_int_adjustment_update,
&bmvals.yofs);
break;
default:
return FALSE;
}
gimp_preview_invalidate (preview);
return TRUE;
}
break;
default:
break;
}
return FALSE;
}
static void
dialog_new_bumpmap (gboolean init_offsets)
{
/* Get drawable */
if (bmint.bm_drawable && (bmint.bm_drawable != drawable))
gimp_drawable_detach (bmint.bm_drawable);
if (bmvals.bumpmap_id != -1)
bmint.bm_drawable = gimp_drawable_get (bmvals.bumpmap_id);
else
bmint.bm_drawable = drawable;
if (!bmint.bm_drawable)
return;
/* Get sizes */
bmint.bm_width = gimp_drawable_width (bmint.bm_drawable->drawable_id);
bmint.bm_height = gimp_drawable_height (bmint.bm_drawable->drawable_id);
bmint.bm_bpp = gimp_drawable_bpp (bmint.bm_drawable->drawable_id);
bmint.bm_has_alpha = gimp_drawable_has_alpha (bmint.bm_drawable->drawable_id);
if (init_offsets)
{
GtkAdjustment *adj;
gint bump_offset_x;
gint bump_offset_y;
gint draw_offset_y;
gint draw_offset_x;
gimp_drawable_offsets (bmint.bm_drawable->drawable_id,
&bump_offset_x, &bump_offset_y);
gimp_drawable_offsets (drawable->drawable_id,
&draw_offset_x, &draw_offset_y);
bmvals.xofs = draw_offset_x - bump_offset_x;
bmvals.yofs = draw_offset_y - bump_offset_y;
adj = (GtkAdjustment *) bmint.offset_adj_x;
if (adj)
{
g_signal_handlers_block_by_func (adj,
gimp_int_adjustment_update,
&bmvals.xofs);
gtk_adjustment_set_value (adj, bmvals.xofs);
g_signal_handlers_unblock_by_func (adj,
gimp_int_adjustment_update,
&bmvals.xofs);
}
adj = (GtkAdjustment *) bmint.offset_adj_y;
if (adj)
{
g_signal_handlers_block_by_func (adj,
gimp_int_adjustment_update,
&bmvals.yofs);
gtk_adjustment_set_value (adj, bmvals.yofs);
g_signal_handlers_unblock_by_func (adj,
gimp_int_adjustment_update,
&bmvals.yofs);
}
}
/* Initialize pixel region */
gimp_pixel_rgn_init (&bmint.bm_rgn, bmint.bm_drawable,
0, 0, bmint.bm_width, bmint.bm_height, FALSE, FALSE);
}
static void
dialog_update_preview (GimpPreview *preview)
{
guchar *dest_row;
gint y;
gint x1, y1;
gint width, height;
gint bytes;
gimp_preview_get_position (preview, &x1, &y1);
gimp_preview_get_size (preview, &width, &height);
bytes = drawable->bpp;
/* Initialize source rows */
gimp_pixel_rgn_init (&bmint.src_rgn, drawable,
sel_x1, sel_y1, sel_width, sel_height, FALSE, FALSE);
bmint.src_rows = g_new (guchar *, height);
for (y = 0; y < height; y++)
bmint.src_rows[y] = g_new (guchar, sel_width * 4);
dialog_fill_src_rows (0, height, y1);
/* Initialize bumpmap rows */
bmint.bm_rows = g_new (guchar *, height + 2);
for (y = 0; y < height + 2; y++)
bmint.bm_rows[y] = g_new (guchar, bmint.bm_width * bmint.bm_bpp);
bumpmap_init_params (&bmint.params);
dialog_fill_bumpmap_rows (0, height, bmvals.yofs + y1);
dest_row = g_new (guchar, width * height * 4);
/* Bumpmap */
for (y = 0; y < height; y++)
{
gint isfirst = ((y == - bmvals.yofs - y1)
&& ! bmvals.tiled) ? 1 : 0;
gint islast = (y == (- bmvals.yofs - y1
+ bmint.bm_height - 1) && ! bmvals.tiled) ? 1 : 0;
bumpmap_row (bmint.src_rows[y] + 4 * x1,
dest_row + 4 * width * y,
width, 4, TRUE,
bmint.bm_rows[y + isfirst],
bmint.bm_rows[y + 1],
bmint.bm_rows[y + 2 - islast],
bmint.bm_width, bmvals.xofs + x1,
bmvals.tiled,
y >= - bmvals.yofs - y1 &&
y < (- bmvals.yofs - y1 + bmint.bm_height),
&bmint.params);
}
gimp_preview_area_draw (GIMP_PREVIEW_AREA (preview->area),
0, 0, width, height,
GIMP_RGBA_IMAGE,
dest_row,
4 * width);
g_free (dest_row);
for (y = 0; y < height + 2; y++)
g_free (bmint.bm_rows[y]);
g_free (bmint.bm_rows);
for (y = 0; y < height; y++)
g_free (bmint.src_rows[y]);
g_free (bmint.src_rows);
}
static void
dialog_get_rows (GimpPixelRgn *pr,
guchar **rows,
gint x,
gint y,
gint width,
gint height)
{
/* This is shamelessly ripped off from gimp_pixel_rgn_get_rect().
* Its function is exactly the same, but it can fetch an image
* rectangle to a sparse buffer which is defined as separate
* rows instead of one big linear region.
*/
gint xstart, ystart;
gint xend, yend;
gint xboundary;
gint yboundary;
gint xstep, ystep;
gint b, bpp;
gint tx, ty;
gint tile_width = gimp_tile_width();
gint tile_height = gimp_tile_height();
bpp = pr->bpp;
xstart = x;
ystart = y;
xend = x + width;
yend = y + height;
ystep = 0; /* Shut up -Wall */
while (y < yend)
{
x = xstart;
while (x < xend)
{
GimpTile *tile;
tile = gimp_drawable_get_tile2 (pr->drawable, pr->shadow, x, y);
gimp_tile_ref (tile);
xstep = tile->ewidth - (x % tile_width);
ystep = tile->eheight - (y % tile_height);
xboundary = x + xstep;
yboundary = y + ystep;
xboundary = MIN (xboundary, xend);
yboundary = MIN (yboundary, yend);
for (ty = y; ty < yboundary; ty++)
{
const guchar *src;
guchar *dest;
src = tile->data + tile->bpp * (tile->ewidth *
(ty % tile_height) +
(x % tile_width));
dest = rows[ty - ystart] + bpp * (x - xstart);
for (tx = x; tx < xboundary; tx++)
for (b = bpp; b; b--)
*dest++ = *src++;
}
gimp_tile_unref (tile, FALSE);
x += xstep;
}
y += ystep;
}
}
static void
dialog_fill_src_rows (gint start,
gint how_many,
gint yofs)
{
gint x;
gint y;
dialog_get_rows (&bmint.src_rgn,
bmint.src_rows + start,
0/*sel_x1*/,
yofs,
sel_width,
how_many);
/* Convert to RGBA. We move backwards! */
for (y = start; y < (start + how_many); y++)
{
const guchar *sp = bmint.src_rows[y] + img_bpp * sel_width - 1;
guchar *p = bmint.src_rows[y] + 4 * sel_width - 1;
for (x = 0; x < sel_width; x++)
{
if (img_has_alpha)
*p-- = *sp--;
else
*p-- = 255;
if (img_bpp < 3)
{
*p-- = *sp;
*p-- = *sp;
*p-- = *sp--;
}
else
{
*p-- = *sp--;
*p-- = *sp--;
*p-- = *sp--;
}
}
}
}
static void
dialog_fill_bumpmap_rows (gint start,
gint how_many,
gint yofs)
{
gint buf_row_ofs;
gint remaining;
gint this_pass;
/* Adapt to offset of selection */
yofs = MOD (yofs + sel_y1, bmint.bm_height);
buf_row_ofs = start;
remaining = how_many;
while (remaining > 0)
{
this_pass = MIN (remaining, bmint.bm_height - yofs);
dialog_get_rows (&bmint.bm_rgn,
bmint.bm_rows + buf_row_ofs,
0,
yofs,
bmint.bm_width,
this_pass);
yofs = (yofs + this_pass) % bmint.bm_height;
remaining -= this_pass;
buf_row_ofs += this_pass;
}
/* Convert rows */
for (; how_many; how_many--)
{
bumpmap_convert_row (bmint.bm_rows[start],
bmint.bm_width,
bmint.bm_bpp,
bmint.bm_has_alpha,
bmint.params.lut);
start++;
}
}
static gboolean
dialog_constrain (gint32 image_id,
gint32 drawable_id,
gpointer data)
{
return (gimp_drawable_is_rgb (drawable_id) ||
gimp_drawable_is_gray (drawable_id));
}
static void
dialog_bumpmap_callback (GtkWidget *widget,
GimpPreview *preview)
{
gint32 drawable_id;
gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), &drawable_id);
if (bmvals.bumpmap_id != drawable_id)
{
bmvals.bumpmap_id = drawable_id;
dialog_new_bumpmap (TRUE);
gimp_preview_invalidate (preview);
}
}
static void
dialog_maptype_callback (GtkWidget *widget,
GimpPreview *preview)
{
gint32 maptype;
gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), &maptype);
if (bmvals.type != maptype)
{
bmvals.type = maptype;
bumpmap_init_params (&bmint.params);
gimp_preview_invalidate (preview);
}
}