/* Bump Map 1.05 --- image filter plug-in for The Gimp image manipulation program * Copyright (C) 1996 Federico Mena Quintero * * You can contact me at quartic@polloux.fciencias.unam.mx * You can contact the original The Gimp authors at gimp@xcf.berkeley.edu * * 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 2 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, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ /* 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. Bugs: - Due to the current limitations in The Gimp's plug-in API, you can only select bump maps which are the same size as the destination image. Otherwise, the filter would do automatic tiling of images. You can work around this by using the Tile plug-in to create a properly-sized image before applying this filter. Other than that, the plug-in seems to work OK :) */ /* Using an RGB bumpmap will actually just use the red channel. - Stephen (srn@cs.su.oz.au) */ #include #include #include #include #include "gimp.h" /* Some useful macros */ #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define WITHIN(a, b, c) ((((a) <= (b)) && ((b) <= (c))) ? 1 : 0) #ifndef _AIX typedef unsigned char uchar; #endif /***** Local functions *****/ static void image_menu_callback(int item_id, void *client_data, void *call_data); static void double_callback(int item_id, void *client_data, void *call_data); static void long_callback(int item_id, void *client_data, void *call_data); static void ok_callback(int item_id, void *client_data, void *call_data); static void cancel_callback(int item_id, void *client_data, void *call_data); static void do_bump_map(Image source_img, Image dest_img, Image bump_map, double azimuth, double elevation, long depth, long xofs, long yofs); /***** Local vars *****/ static char *prog_name; static int dialog_id; typedef struct { long bump_map_image_id; double azimuth, elevation; long depth; long xofs, yofs; } bump_map_values; bump_map_values values; /***** Functions *****/ /*****/ int main(int argc, char **argv) { Image source, dest, bump_map; void *data; int bump_map_menu_id; int azimuth_id, elevation_id; int depth_id; int xofs_id, yofs_id; int group_id, temp_id; char buf[100]; /* Save program name */ prog_name = argv[0]; /* Initialize filter and continue if success */ if (!gimp_init(argc, argv)) return 0; source = gimp_get_input_image(0); dest = gimp_get_output_image(0); if (source && dest) { if ((gimp_image_type(source) == RGB_IMAGE) || (gimp_image_type(source) == GRAY_IMAGE)) { data = gimp_get_params(); if (data) { values = *((bump_map_values *) data); values.bump_map_image_id = 0; } else { values.bump_map_image_id = 0; values.azimuth = 135.0; values.elevation = 45.0; values.depth = 3; values.xofs = 0; values.yofs = 0; } /* else */ bump_map = NULL; /* Create the dialog */ dialog_id = gimp_new_dialog("Bump Map"); gimp_new_label(dialog_id, DEFAULT, "Options"); group_id = gimp_new_row_group(dialog_id, DEFAULT, NORMAL, ""); bump_map_menu_id = gimp_new_image_menu(dialog_id, group_id, IMAGE_CONSTRAIN_GRAY|IMAGE_CONSTRAIN_RGB, "Bump map image"); temp_id = gimp_new_column_group(dialog_id, group_id, NORMAL, ""); gimp_new_label(dialog_id, temp_id, "Light azimuth:"); sprintf(buf, "%0.3f", values.azimuth); azimuth_id = gimp_new_text(dialog_id, temp_id, buf); temp_id = gimp_new_column_group(dialog_id, group_id, NORMAL, ""); gimp_new_label(dialog_id, temp_id, "Light elevation:"); sprintf(buf, "%0.3f", values.elevation); elevation_id = gimp_new_text(dialog_id, temp_id, buf); temp_id = gimp_new_column_group(dialog_id, group_id, NORMAL, ""); gimp_new_label(dialog_id, temp_id, "Bump depth:"); sprintf(buf, "%ld", values.depth); depth_id = gimp_new_text(dialog_id, temp_id, buf); temp_id = gimp_new_column_group(dialog_id, group_id, NORMAL, ""); gimp_new_label(dialog_id, temp_id, "Bump x offset:"); sprintf(buf, "%ld", values.xofs); xofs_id = gimp_new_text(dialog_id, temp_id, buf); temp_id = gimp_new_column_group(dialog_id, group_id, NORMAL, ""); gimp_new_label(dialog_id, temp_id, "Bump y offset:"); sprintf(buf, "%ld", values.yofs); yofs_id = gimp_new_text(dialog_id, temp_id, buf); gimp_add_callback(dialog_id, bump_map_menu_id, image_menu_callback, &values.bump_map_image_id); gimp_add_callback(dialog_id, azimuth_id, double_callback, &values.azimuth); gimp_add_callback(dialog_id, elevation_id, double_callback, &values.elevation); gimp_add_callback(dialog_id, depth_id, long_callback, &values.depth); gimp_add_callback(dialog_id, xofs_id, long_callback, &values.xofs); gimp_add_callback(dialog_id, yofs_id, long_callback, &values.yofs); gimp_add_callback(dialog_id, gimp_ok_item_id(dialog_id), ok_callback, NULL); gimp_add_callback(dialog_id, gimp_cancel_item_id(dialog_id), cancel_callback, NULL); if (gimp_show_dialog(dialog_id)) { gimp_set_params(sizeof(values), &values); bump_map = gimp_get_input_image(values.bump_map_image_id); if (bump_map) { gimp_init_progress("Bump Map"); do_bump_map(source, dest, bump_map, values.azimuth, values.elevation, values.depth, values.xofs, values.yofs); gimp_update_image(dest); if (bump_map != source) gimp_free_image(bump_map); } /* if */ } /* if */ } else gimp_message("Bump Map: can only operate on 24-bit or grayscale images"); gimp_free_image(source); gimp_free_image(dest); } /* if */ gimp_quit(); return 0; } /* main */ /*****/ static void image_menu_callback(int item_id, void *client_data, void *call_data) { *((long *) client_data) = *((long *) call_data); } /* image_menu_callback */ /*****/ static void double_callback(int item_id, void *client_data, void *call_data) { *((double *) client_data) = atof(call_data); } /* double_callback */ /*****/ static void long_callback(int item_id, void *client_data, void *call_data) { *((long *) client_data) = atol(call_data); } /* long_callback */ /*****/ static void ok_callback(int item_id, void *client_data, void *call_data) { gimp_close_dialog(dialog_id, 1); } /* ok_callback */ /*****/ static void cancel_callback(int item_id, void *client_data, void *call_data) { gimp_close_dialog(dialog_id, 0); } /* cancel_callback */ /*****/ static void do_bump_map(Image source_img, Image dest_img, Image bump_map, double azimuth, double elevation, long depth, long xofs, long yofs) { long src_width, src_height; long bm_width, bm_height, bm_channels; long channels, rowsiz, bm_rowsiz; uchar *src, *dest, *bm; uchar *bm_l1, *bm_l2, *bm_l3; uchar *src_line, *dest_line; long xofs1, xofs2, xofs3; long yofs1, yofs2, yofs3; int progress, max_progress; long k; int x1, y1, x2, y2; long nx, ny, nz; /* surface normal */ long lx, ly, lz; /* light vector */ long nz2, nzlz, ndotl; /* nz^2, nz * lz, vector_dot(normal, light) */ uchar shade, background; long x, y; /* Get selection area */ gimp_image_area(source_img, &x1, &y1, &x2, &y2); src_width = gimp_image_width(source_img); src_height = gimp_image_height(source_img); channels = gimp_image_channels(source_img); rowsiz = src_width * channels; bm_width = gimp_image_width(bump_map); bm_height = gimp_image_height(bump_map); bm_channels = gimp_image_channels(bump_map); bm_rowsiz = bm_width * bm_channels; /* Get image data */ src = (uchar *) gimp_image_data(source_img) + channels * (src_width * y1 + x1); dest = (uchar *) gimp_image_data(dest_img) + channels * (src_width * y1 + x1); bm = gimp_image_data(bump_map); /* Initialize progress */ progress = 0; max_progress = y2 - y1; /* Calculate light vector */ azimuth = M_PI * azimuth / 180.0; /* Convert to radians */ elevation = M_PI * elevation / 180.0; lx = cos(azimuth) * cos(elevation) * 255.0; ly = sin(azimuth) * cos(elevation) * 255.0; lz = sin(elevation) * 255.0; /* Calculate constant z component of surface normal */ nz = (6 * 255) / depth; nz2 = nz * nz; nzlz = nz * lz; /* Optimize for vertical normals */ background = lz; /* Bump map the image! */ if (xofs < 0) xofs2 = bm_width - (-xofs % bm_width); else xofs2 = xofs % bm_width; if (yofs < 0) yofs2 = bm_height - (-yofs % bm_height); else yofs2 = yofs % bm_height; xofs1 = (xofs2 + bm_width - 1) % bm_width; xofs3 = (xofs2 + 1) % bm_width; xofs1 *= bm_channels; xofs2 *= bm_channels; xofs3 *= bm_channels; yofs1 = (yofs2 + bm_height - 1) % bm_height; yofs3 = (yofs2 + 1) % bm_height; for (y = y1; y < y2; y++) { src_line = src; dest_line = dest; bm_l1 = bm + (bm_rowsiz * yofs1); bm_l2 = bm + (bm_rowsiz * yofs2); bm_l3 = bm + (bm_rowsiz * yofs3); for (x = x1; x < x2; x++) { /* Calculate surface normal from bump map */ nx = (long) (bm_l1[xofs1] + bm_l2[xofs1] + bm_l3[xofs1] - bm_l1[xofs3] - bm_l2[xofs3] - bm_l3[xofs3]); ny = (long) (bm_l3[xofs1] + bm_l3[xofs2] + bm_l3[xofs3] - bm_l1[xofs1] - bm_l1[xofs2] - bm_l1[xofs3]); /* Shade */ if ((nx == 0) && (ny == 0)) shade = background; else { ndotl = nx * lx + ny * ly + nz * lz; if (ndotl < 0) shade = 0; else shade = ndotl / sqrt(nx * nx + ny * ny + nz2); } /* else */ /* Paint */ for (k = channels; k; k--) *dest_line++ = *src_line++ * shade / 255; /* Next pixel! */ xofs1 += bm_channels; if (xofs1 == bm_rowsiz) xofs1 = 0; xofs2 += bm_channels; if (xofs2 == bm_rowsiz) xofs2 = 0; xofs3 += bm_channels; if (xofs3 == bm_rowsiz) xofs3 = 0; } /* for */ /* Next line! */ if (++yofs1 == bm_height) yofs1 = 0; if (++yofs2 == bm_height) yofs2 = 0; if (++yofs3 == bm_height) yofs3 = 0; src += rowsiz; dest += rowsiz; /* Update progress */ progress++; if (progress % 5 == 0) gimp_do_progress(progress, max_progress); } /* for */ } /* do_bump_map */