From 8ca3cca5dccd9cfa7115f46fcb514b9796c44eef Mon Sep 17 00:00:00 2001 From: Manish Singh Date: Thu, 22 Oct 1998 11:39:32 +0000 Subject: [PATCH] fixed a buglet that caused an FP exception in PsychoBilly mode * plug-ins/checkerboard/checkerboard.c: fixed a buglet that caused an FP exception in PsychoBilly mode * added borderaverage, wind, jigsaw, and newsprint plug-ins -Yosh --- ChangeLog | 7 + configure.in | 4 + plug-ins/Makefile.am | 4 + plug-ins/checkerboard/checkerboard.c | 4 + plug-ins/common/borderaverage.c-28877 | 357 ++++ plug-ins/common/checkerboard.c | 4 + plug-ins/common/jigsaw.c | 2708 +++++++++++++++++++++++++ plug-ins/common/newsprint.c | 2237 ++++++++++++++++++++ plug-ins/common/wind.c | 1105 ++++++++++ plug-ins/jigsaw/.cvsignore | 6 + plug-ins/jigsaw/Makefile.am | 35 + plug-ins/jigsaw/jigsaw.c | 2708 +++++++++++++++++++++++++ plug-ins/newsprint/.cvsignore | 6 + plug-ins/newsprint/Makefile.am | 35 + plug-ins/newsprint/newsprint.c | 2237 ++++++++++++++++++++ plug-ins/wind/.cvsignore | 6 + plug-ins/wind/Makefile.am | 35 + plug-ins/wind/wind.c | 1105 ++++++++++ 18 files changed, 12603 insertions(+) create mode 100644 plug-ins/common/borderaverage.c-28877 create mode 100644 plug-ins/common/jigsaw.c create mode 100644 plug-ins/common/newsprint.c create mode 100644 plug-ins/common/wind.c create mode 100644 plug-ins/jigsaw/.cvsignore create mode 100644 plug-ins/jigsaw/Makefile.am create mode 100644 plug-ins/jigsaw/jigsaw.c create mode 100644 plug-ins/newsprint/.cvsignore create mode 100644 plug-ins/newsprint/Makefile.am create mode 100644 plug-ins/newsprint/newsprint.c create mode 100644 plug-ins/wind/.cvsignore create mode 100644 plug-ins/wind/Makefile.am create mode 100644 plug-ins/wind/wind.c diff --git a/ChangeLog b/ChangeLog index e04a355efb..d8dfc4960a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +Thu Oct 22 04:35:43 PDT 1998 Manish Singh + + * plug-ins/checkerboard/checkerboard.c: fixed a buglet that + caused an FP exception in PsychoBilly mode + + * added borderaverage, wind, jigsaw, and newsprint plug-ins + Thu Oct 22 02:30:38 PDT 1998 Manish Singh * libgimp/Makefile.am diff --git a/configure.in b/configure.in index 2170df1865..8c27cb9f48 100644 --- a/configure.in +++ b/configure.in @@ -543,6 +543,7 @@ plug-ins/autostretch_hsv/Makefile plug-ins/blinds/Makefile plug-ins/blur/Makefile plug-ins/bmp/Makefile +plug-ins/borderaverage/Makefile plug-ins/bumpmap/Makefile plug-ins/bz2/Makefile plug-ins/c_astretch/Makefile @@ -591,12 +592,14 @@ plug-ins/hrz/Makefile plug-ins/ifscompose/Makefile plug-ins/illusion/Makefile plug-ins/iwarp/Makefile +plug-ins/jigsaw/Makefile plug-ins/laplace/Makefile plug-ins/mail/Makefile plug-ins/max_rgb/Makefile plug-ins/maze/Makefile plug-ins/mblur/Makefile plug-ins/mosaic/Makefile +plug-ins/newsprint/Makefile plug-ins/nlfilt/Makefile plug-ins/noisify/Makefile plug-ins/normalize/Makefile @@ -646,6 +649,7 @@ plug-ins/vpropagate/Makefile plug-ins/waterselect/Makefile plug-ins/waves/Makefile plug-ins/whirlpinch/Makefile +plug-ins/wind/Makefile plug-ins/xwd/Makefile plug-ins/zealouscrop/Makefile app/Makefile diff --git a/plug-ins/Makefile.am b/plug-ins/Makefile.am index 87338575e9..573b9804b3 100644 --- a/plug-ins/Makefile.am +++ b/plug-ins/Makefile.am @@ -28,6 +28,7 @@ SUBDIRS = \ blinds \ blur \ bmp \ + borderaverage \ bumpmap \ bz2 \ c_astretch \ @@ -75,12 +76,14 @@ SUBDIRS = \ ifscompose \ illusion \ iwarp \ + jigsaw \ laplace \ mail \ max_rgb \ maze \ mblur \ mosaic \ + newsprint \ nlfilt \ noisify \ normalize \ @@ -130,6 +133,7 @@ SUBDIRS = \ waterselect \ waves \ whirlpinch \ + wind \ xwd \ zealouscrop diff --git a/plug-ins/checkerboard/checkerboard.c b/plug-ins/checkerboard/checkerboard.c index bd517458d8..5de2521686 100644 --- a/plug-ins/checkerboard/checkerboard.c +++ b/plug-ins/checkerboard/checkerboard.c @@ -301,6 +301,10 @@ inblock( gint pos, gint size) static gint *in = NULL; /* initialized first time */ gint i,j,k, len; + /* avoid a FP exception */ + if (size == 1) + size = 2; + len = size*size; /* * Initialize the array; since we'll be called thousands of diff --git a/plug-ins/common/borderaverage.c-28877 b/plug-ins/common/borderaverage.c-28877 new file mode 100644 index 0000000000..a914abd524 --- /dev/null +++ b/plug-ins/common/borderaverage.c-28877 @@ -0,0 +1,357 @@ +/* borderaverage 0.01 - image processing plug-in for the Gimp 1.0 API + * + * Copyright (C) 1998 Philipp Klaus (webmaster@access.ch) + * + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include +#include +#include + + + +/* Declare local functions. + */ +static void query (void); +static void run (char *name, + int nparams, + GParam *param, + int *nreturn_vals, + GParam **return_vals); + +static void borderaverage(GDrawable *drawable, guchar *res_r, guchar *res_g, guchar *res_b); + +static gint borderaverage_dialog(void); + +static void add_new_color (gint bytes, guchar* buffer, gint* cube, gint bucket_expo); + +void menu_callback(GtkWidget *widget, gpointer client_data); + +GPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +static gint borderaverage_thickness = 3; +static gint borderaverage_bucket_exponent = 4; + +struct borderaverage_data { + gint thickness; + gint bucket_exponent; + } borderaverage_data = { 3, 4 }; + +gchar * menu_labels[] = + { + "1 (nonsens?)", "2", "4", "8", "16", "32", "64", "128", "256 (nonsens?)" + }; + +MAIN () + +static void +query () +{ + static GParamDef args[] = + { + { PARAM_INT32, "run_mode", "Interactive, non-interactive" }, + { PARAM_IMAGE, "image", "Input image (unused)" }, + { PARAM_DRAWABLE, "drawable", "Input drawable" }, + { PARAM_INT32, "thickness", "Border size to take in count" }, + { PARAM_INT32, "bucket_exponent", "Bits for bucket size (default=4: 16 Levels)" }, + }; + static GParamDef return_vals[] = + { + { PARAM_INT32, "num_channels", "Number of color channels returned (always 3)" }, + { PARAM_INT8ARRAY, "color_vals", "The average color of the specified border"}, + }; + static int nargs = sizeof (args) / sizeof (args[0]); + static int nreturn_vals = sizeof (return_vals) / sizeof (return_vals[0]); + + gimp_install_procedure ("plug_in_borderaverage", + "Borderaverage", + "", + "Philipp Klaus", + "Internet Access AG", + "1998", + "/Filters/Colors/Border Average", + "RGB*", + PROC_PLUG_IN, + nargs, nreturn_vals, + args, return_vals); +} + +static void +run (char *name, + int nparams, + GParam *param, + int *nreturn_vals, + GParam **return_vals) +{ + static GParam values[3]; + GDrawable *drawable; + GRunModeType run_mode; + GStatusType status = STATUS_SUCCESS; + gint8 *result_color; + + run_mode = param[0].data.d_int32; + + /* get the return memory */ + result_color = (gint8 *) g_new(gint8, 3); + + /* Get the specified drawable */ + drawable = gimp_drawable_get (param[2].data.d_drawable); + + switch (run_mode) { + case RUN_INTERACTIVE: + gimp_get_data("plug_in_borderaverage", &borderaverage_data); + borderaverage_thickness = borderaverage_data.thickness; + borderaverage_bucket_exponent = borderaverage_data.bucket_exponent; + if (! borderaverage_dialog()) + status = STATUS_EXECUTION_ERROR; + break; + + case RUN_NONINTERACTIVE: + if (nparams != 5) + status = STATUS_CALLING_ERROR; + if (status == STATUS_SUCCESS) + borderaverage_thickness = param[3].data.d_int32; + borderaverage_bucket_exponent = param[4].data.d_int32; + + break; + + case RUN_WITH_LAST_VALS: + gimp_get_data("plug_in_borderaverage", &borderaverage_data); + borderaverage_thickness = borderaverage_data.thickness; + borderaverage_bucket_exponent = borderaverage_data.bucket_exponent; + break; + + default: + break; + } + + if (status == STATUS_SUCCESS) { + /* Make sure that the drawable is RGB color */ + if (gimp_drawable_color (drawable->id)) { + gimp_progress_init ("borderaverage"); + borderaverage (drawable, &result_color[0], &result_color[1], &result_color[2]); + + if (run_mode != RUN_NONINTERACTIVE) { + gimp_palette_set_foreground(result_color[0],result_color[1],result_color[2]); + gimp_displays_flush (); + } + if (run_mode == RUN_INTERACTIVE) + borderaverage_data.thickness = borderaverage_thickness; + borderaverage_data.bucket_exponent = borderaverage_bucket_exponent; + gimp_set_data("plug_in_borderaverage", &borderaverage_data, sizeof(borderaverage_data)); + } else { + status = STATUS_EXECUTION_ERROR; + } + } + *nreturn_vals = 3; + *return_vals = values; + + values[0].type = PARAM_STATUS; + values[0].data.d_status = status; + + values[1].type = PARAM_INT32; + values[1].data.d_int32 = 3; + + values[2].type = PARAM_INT8ARRAY; + values[2].data.d_int8array = result_color; + + gimp_drawable_detach (drawable); +} + +static void +borderaverage (GDrawable *drawable, guchar *res_r, guchar *res_g, guchar *res_b) { + gint width; + gint height; + gint x1, x2, y1, y2; + gint bytes; + gint max; + + guchar r, g, b; + + guchar *buffer; + gint bucket_num, bucket_expo, bucket_rexpo; + + gint* cube; + + gint row, col, i,j,k; /* index variables */ + + GPixelRgn myPR; + + + /* allocate and clear the cube before */ + bucket_expo = borderaverage_bucket_exponent; + bucket_rexpo = 8 - bucket_expo; + cube = (gint*) malloc( (1 << (bucket_rexpo * 3)) * sizeof(gint) ); + bucket_num = 1 << bucket_rexpo; + + for (i = 0; i < bucket_num; i++) { + for (j = 0; j < bucket_num; j++) { + for (k = 0; k < bucket_num; k++) { + cube[(i << (bucket_rexpo << 1)) + (j << bucket_rexpo) + k] = 0; + } + } + } + + + /* Get the input area. This is the bounding box of the selection in + * the image (or the entire image if there is no selection). Only + * operating on the input area is simply an optimization. It doesn't + * need to be done for correct operation. (It simply makes it go + * faster, since fewer pixels need to be operated on). + */ + + gimp_drawable_mask_bounds (drawable->id, &x1, &y1, &x2, &y2); + + /* Get the size of the input image. (This will/must be the same + * as the size of the output image. + */ + width = drawable->width; + height = drawable->height; + bytes = drawable->bpp; + + /* allocate row buffer */ + buffer = (guchar *) malloc ((x2 - x1) * bytes); + + /* initialize the pixel regions */ + gimp_pixel_rgn_init (&myPR, drawable, 0, 0, width, height, FALSE, FALSE); + + /* loop through the rows, performing our magic*/ + for (row = y1; row < y2; row++) { + + gimp_pixel_rgn_get_row (&myPR, buffer, x1, row, (x2-x1)); + + if (row < y1 + borderaverage_thickness || + row >= y2 - borderaverage_thickness) { + /* add the whole row */ + for (col = 0; col < ((x2 - x1) * bytes); col += bytes) { + add_new_color(bytes, &buffer[col], cube, bucket_expo); + } + } else { + /* add the left border */ + for (col = 0; col < (borderaverage_thickness * bytes); col += bytes) { + add_new_color(bytes, &buffer[col], cube, bucket_expo); + } + /* add the right border */ + for (col = ((x2 - x1 - borderaverage_thickness) * bytes); + col < ((x2 - x1) * bytes); col += bytes) { + add_new_color(bytes, &buffer[col], cube, bucket_expo); + } + } + + + if ((row % 5) == 0) + gimp_progress_update ((double) row / (double) (y2 - y1)); + } + + max = 0; r = 0; g = 0; b = 0; + + /* get max of cube */ + for (i = 0; i < bucket_num; i++) { + for (j = 0; j < bucket_num; j++) { + for (k = 0; k < bucket_num; k++) { + if (cube[(i << (bucket_rexpo << 1)) + (j << bucket_rexpo) + k] > max) { + max = cube[(i << (bucket_rexpo << 1)) + (j << bucket_rexpo) + k]; + r = (i<>bucket_expo; + if (bytes > 1) { + g = buffer[1] >>bucket_expo; + } else { + g = 0; + } + if (bytes > 2) { + b = buffer[2] >>bucket_expo; + } else { + b = 0; + } + cube[(r << (bucket_rexpo << 1)) + (g << bucket_rexpo) + b]++; +} + +static gint borderaverage_dialog() { + GtkWidget *dlg, *frame, *vbox2; + GtkWidget *vbox, *menu; + gint runp; + gchar **argv; + gint argc; + + /* Set args */ + argc = 1; + argv = g_new(gchar *, 1); + argv[0] = g_strdup("borderaverage"); + gtk_init(&argc, &argv); + gtk_rc_parse(gimp_gtkrc()); + + dlg = mw_app_new("plug_in_borderaverage", "Borderaverage", &runp); + + vbox = gtk_vbox_new(FALSE, 0); + gtk_container_border_width(GTK_CONTAINER(vbox), 5); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->vbox), vbox, TRUE, TRUE, 0); + gtk_widget_show(vbox); + + mw_ientry_new(vbox, "Border Size", "Thickness", &borderaverage_thickness); + + + frame=gck_frame_new("Number of colors",vbox,GTK_SHADOW_ETCHED_IN,FALSE,FALSE,0,2); + vbox2=gck_vbox_new(frame,FALSE,TRUE,TRUE,5,0,5); + + menu=gck_option_menu_new("Bucket Size:",vbox2,TRUE,TRUE,0, + menu_labels,(GtkSignalFunc)menu_callback, NULL); + gtk_option_menu_set_history(GTK_OPTION_MENU(menu),borderaverage_bucket_exponent); + gtk_widget_show(menu); + + gtk_widget_show(dlg); + gtk_main(); + gdk_flush(); + + if (runp) + return 1; + else + return 0; +} + + +void menu_callback(GtkWidget *widget, gpointer client_data) { + borderaverage_bucket_exponent=(gint)gtk_object_get_data(GTK_OBJECT(widget),"_GckOptionMenuItemID"); +} diff --git a/plug-ins/common/checkerboard.c b/plug-ins/common/checkerboard.c index bd517458d8..5de2521686 100644 --- a/plug-ins/common/checkerboard.c +++ b/plug-ins/common/checkerboard.c @@ -301,6 +301,10 @@ inblock( gint pos, gint size) static gint *in = NULL; /* initialized first time */ gint i,j,k, len; + /* avoid a FP exception */ + if (size == 1) + size = 2; + len = size*size; /* * Initialize the array; since we'll be called thousands of diff --git a/plug-ins/common/jigsaw.c b/plug-ins/common/jigsaw.c new file mode 100644 index 0000000000..b9aa183a6e --- /dev/null +++ b/plug-ins/common/jigsaw.c @@ -0,0 +1,2708 @@ +/* + * jigsaw - a plug-in for the GIMP + * + * Copyright (C) Nigel Wetten + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Contact info: nigel@cs.nwu.edu + * + * Version: 1.0.0 + */ + +#include +#include + +#include +#include "libgimp/gimp.h" + +typedef enum {BEZIER_1, BEZIER_2} style_t; +typedef enum {LEFT, RIGHT, UP, DOWN} bump_t; + + +static void query(void); +static void run(gchar *name, gint nparams, GParam *param, + gint *nreturn_vals, GParam **return_vals); +static gint jigsaw(void); +static void run_callback(GtkWidget *widget, gpointer data); +static void dialog_box(void); +static void dialog_close_callback(GtkWidget *widget, gpointer data); +static void entry_callback(GtkWidget *widget, gpointer data); +static void entry_double_callback(GtkWidget *widget, gpointer data); +static void radio_button_primitive_callback(GtkWidget *widget, gpointer data); +static void adjustment_callback(GtkAdjustment *adjustment, gpointer data); +static void adjustment_double_callback(GtkAdjustment *adjustment, + gpointer data); +static void check_button_callback(GtkWidget *widget, gpointer data); + +static void draw_jigsaw(guchar *buffer, gint width, gint height, gint bytes); +static void draw_vertical_border(guchar *buffer, gint width, gint height, + gint bytes, gint x_offset, gint ytiles, + gint blend_lines, gdouble blend_amount); +static void draw_horizontal_border(guchar *buffer, gint width, + gint bytes, gint y_offset, gint xtiles, + gint blend_lines, gdouble blend_amount); +static void draw_vertical_line(guchar *buffer, gint width, gint bytes, + gint px[2], gint py[2]); +static void draw_horizontal_line(guchar *buffer, gint width, gint bytes, + gint px[2], gint py[2]); +static void darken_vertical_line(guchar *buffer, gint width, gint bytes, + gint *px, gint *py, gdouble delta); +static void lighten_vertical_line(guchar *buffer, gint width, gint bytes, + gint *px, gint *py, gdouble delta); +static void darken_horizontal_line(guchar *buffer, gint width, gint bytes, + gint *px, gint *py, gdouble delta); +static void lighten_horizontal_line(guchar *buffer, gint width, gint bytes, + gint *px, gint *py, gdouble delta); +static void draw_right_bump(guchar *buffer, gint width, gint bytes, + gint x_offset, gint curve_start_offset, + gint steps); +static void draw_left_bump(guchar *buffer, gint width, gint bytes, + gint x_offset, gint curve_start_offset, + gint steps); +static void draw_up_bump(guchar *buffer, gint width, gint bytes, + gint y_offset, gint curve_start_offset, + gint steps); +static void draw_down_bump(guchar *buffer, gint width, gint bytes, + gint y_offset, gint curve_start_offset, + gint steps); +static void darken_right_bump(guchar *buffer, gint width, gint bytes, + gint x_offset, gint curve_start_offset, + gint steps, gdouble delta, gint counter); +static void lighten_right_bump(guchar *buffer, gint width, gint bytes, + gint x_offset, gint curve_start_offset, + gint steps, gdouble delta, gint counter); +static void darken_left_bump(guchar *buffer, gint width, gint bytes, + gint x_offset, gint curve_start_offset, + gint steps, gdouble delta, gint counter); +static void lighten_left_bump(guchar *buffer, gint width, gint bytes, + gint x_offset, gint curve_start_offset, + gint steps, gdouble delta, gint counter); +static void darken_up_bump(guchar *buffer, gint width, gint bytes, + gint y_offset, gint curve_start_offest, + gint steps, gdouble delta, gint counter); +static void lighten_up_bump(guchar *buffer, gint width, gint bytes, + gint y_offset, gint curve_start_offset, + gint steps, gdouble delta, gint counter); +static void darken_down_bump(guchar *buffer, gint width, gint bytes, + gint y_offset, gint curve_start_offset, + gint steps, gdouble delta, gint counter); +static void lighten_down_bump(guchar *buffer, gint width, gint bytes, + gint y_offset, gint curve_start_offset, + gint steps, gdouble delta, gint counter); +static void generate_grid(gint width, gint height, gint xtiles, gint ytiles, + gint *x, gint *y); +static void generate_bezier(gint px[4], gint py[4], gint steps, + gint *cachex, gint *cachey); +static void malloc_cache(void); +static void free_cache(void); +static void init_right_bump(gint width, gint height); +static void init_left_bump(gint width, gint height); +static void init_up_bump(gint width, gint height); +static void init_down_bump(gint width, gint height); +static void draw_bezier_line(guchar *buffer, gint width, gint bytes, + gint steps, gint *cx, gint *cy); +static void darken_bezier_line(guchar *buffer, gint width, gint bytes, + gint x_offset, gint y_offset, gint steps, + gint *cx, gint *cy, gdouble delta); +static void lighten_bezier_line(guchar *buffer, gint width, gint bytes, + gint x_offset, gint y_offset, gint steps, + gint *cx, gint *cy, gdouble delta); +static void draw_bezier_vertical_border(guchar *buffer, gint width, + gint height, gint bytes, + gint x_offset, gint xtiles, + gint ytiles, gint blend_lines, + gdouble blend_amount, gint steps); +static void draw_bezier_horizontal_border(guchar *buffer, gint width, + gint height, gint bytes, + gint x_offset, gint xtiles, + gint ytiles, gint blend_lines, + gdouble blend_amount, gint steps); +static void check_config(gint width, gint height); + + + +#define PLUG_IN_NAME "jigsaw" +#define PLUG_IN_STORAGE "jigsaw-storage" + +#define XFACTOR2 0.0833 +#define XFACTOR3 0.2083 +#define XFACTOR4 0.2500 + +#define XFACTOR5 0.2500 +#define XFACTOR6 0.2083 +#define XFACTOR7 0.0833 + +#define YFACTOR2 0.1000 +#define YFACTOR3 0.2200 +#define YFACTOR4 0.1000 + +#define YFACTOR5 0.1000 +#define YFACTOR6 0.4666 +#define YFACTOR7 0.1000 +#define YFACTOR8 0.2000 + +#define MAX_VALUE 255 +#define MIN_VALUE 0 +#define DELTA 0.15 + +#define BLACK_R 30 +#define BLACK_G 30 +#define BLACK_B 30 + +#define WALL_XFACTOR2 0.05 +#define WALL_XFACTOR3 0.05 +#define WALL_YFACTOR2 0.05 +#define WALL_YFACTOR3 0.05 + +#define WALL_XCONS2 0.2 +#define WALL_XCONS3 0.3 +#define WALL_YCONS2 0.2 +#define WALL_YCONS3 0.3 + +#define FUDGE 1.2 + +#define MIN_XTILES 1 +#define MAX_XTILES 20 +#define MIN_YTILES 1 +#define MAX_YTILES 20 +#define MIN_BLEND_LINES 0 +#define MAX_BLEND_LINES 10 +#define MIN_BLEND_AMOUNT 0 +#define MAX_BLEND_AMOUNT 1.0 + +#define SCALE_WIDTH 200 +#define ENTRY_WIDTH 40 + +#define XTILES_TEXT "Number of pieces going across" +#define YTILES_TEXT "Number of pieces going down" +#define BLEND_LINES_TEXT "Degree of slope of each piece's edge" +#define BLEND_AMOUNT_TEXT "The amount of highlighting on the edges of each piece" +#define SQUARE_TEXT "Each piece has straight sides" +#define CURVE_TEXT "Each piece has curved sides" +#define DISABLE_TEXT "Toggle Tooltips on/off" + +#define DRAW_POINT(buffer, index) \ +do { \ + buffer[index] = BLACK_R; \ + buffer[index+1] = BLACK_G; \ + buffer[index+2] = BLACK_B; \ +} while (0) + +#define DARKEN_POINT(buffer, index, delta, temp) \ +do { \ + temp = buffer[index]; \ + temp -= buffer[index] * delta; \ + if (temp < MIN_VALUE) temp = MIN_VALUE; \ + buffer[index] = temp; \ + temp = buffer[index+1]; \ + temp -= buffer[index+1] * delta; \ + if (temp < MIN_VALUE) temp = MIN_VALUE; \ + buffer[index+1] = temp; \ + temp = buffer[index+2]; \ + temp -= buffer[index+2] * delta; \ + if (temp < MIN_VALUE) temp = MIN_VALUE; \ + buffer[index+2] = temp; \ +} while (0) + +#define LIGHTEN_POINT(buffer, index, delta, temp) \ +do { \ + temp = buffer[index] * delta; \ + temp += buffer[index]; \ + if (temp > MAX_VALUE) temp = MAX_VALUE; \ + buffer[index] = temp; \ + temp = buffer[index+1] * delta; \ + temp += buffer[index+1]; \ + if (temp > MAX_VALUE) temp = MAX_VALUE; \ + buffer[index+1] = temp; \ + temp = buffer[index+2] * delta; \ + temp += buffer[index+2]; \ + if (temp > MAX_VALUE) temp = MAX_VALUE; \ + buffer[index+2] = temp; \ +} while (0) + + +GPlugInInfo PLUG_IN_INFO = +{ + NULL, + NULL, + query, + run +}; + +struct config_tag +{ + gint x; + gint y; + style_t style; + gint blend_lines; + gdouble blend_amount; +}; + + +typedef struct config_tag config_t; + +static config_t config = +{ + 5, + 5, + BEZIER_1, + 3, + 0.5 +}; + +struct globals_tag +{ + GDrawable *drawable; + gint *cachex1[4]; + gint *cachex2[4]; + gint *cachey1[4]; + gint *cachey2[4]; + gint steps[4]; + gint *gridx; + gint *gridy; + gint **blend_outer_cachex1[4]; + gint **blend_outer_cachex2[4]; + gint **blend_outer_cachey1[4]; + gint **blend_outer_cachey2[4]; + gint **blend_inner_cachex1[4]; + gint **blend_inner_cachex2[4]; + gint **blend_inner_cachey1[4]; + gint **blend_inner_cachey2[4]; + gint dialog_result; + gint tooltips; +}; + +typedef struct globals_tag globals_t; + +static globals_t globals = +{ + 0, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + 0, + 0, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + -1, + 1 +}; + +MAIN() + +static void +query(void) +{ + static GParamDef args[] = + { + { PARAM_INT32, "run_mode", "Interactive, Non-interactive, Last-Vals"}, + { PARAM_IMAGE, "image", "Input image"}, + { PARAM_DRAWABLE, "drawable", "Input drawable"}, + { PARAM_INT32, "x", "Number of tiles across > 0"}, + { PARAM_INT32, "y", "Number of tiles down > 0"}, + { PARAM_INT32, "style", "The style/shape of the jigsaw puzzle, 0 or 1"}, + { PARAM_INT32, "blend_lines", "Number of lines for shading bevels >= 0"}, + { PARAM_FLOAT, "blend_amount", "The power of the light highlights 0 =< 5"} + }; + static GParamDef *return_vals = NULL; + static gint nargs = sizeof(args) / sizeof(args[0]); + static gint nreturn_vals = 0; + + gimp_install_procedure("plug_in_jigsaw", + "Renders a jigsaw puzzle look", + "Jigsaw puzzle look", + "Nigel Wetten", + "Nigel Wetten", + "1998", + "/Filters/Render/Jigsaw", + "RGB*", + PROC_PLUG_IN, + nargs, nreturn_vals, + args, return_vals); + + return; +} /* query */ + +/*****/ + +static void +run(gchar *name, gint nparams, GParam *param, gint *nreturn_vals, + GParam **return_vals) +{ + static GParam values[1]; + GDrawable *drawable; + GRunModeType run_mode; + GStatusType status = STATUS_SUCCESS; + + run_mode = param[0].data.d_int32; + drawable = gimp_drawable_get(param[2].data.d_drawable); + globals.drawable = drawable; + gimp_tile_cache_ntiles(drawable->width / gimp_tile_width() + 1); + switch (run_mode) + { + case RUN_NONINTERACTIVE: + if (nparams == 8) + { + config.x = param[3].data.d_int32; + config.y = param[4].data.d_int32; + config.style = param[5].data.d_int32; + config.blend_lines = param[6].data.d_int32; + config.blend_amount = param[7].data.d_float; + if (jigsaw() == -1) + { + status = STATUS_EXECUTION_ERROR; + } + } + else + { + status = STATUS_CALLING_ERROR; + } + break; + + case RUN_INTERACTIVE: + gimp_get_data("plug_in_jigsaw", &config); + gimp_get_data(PLUG_IN_STORAGE, &globals.tooltips); + dialog_box(); + if (globals.dialog_result == -1) + { + status = STATUS_EXECUTION_ERROR; + break; + } + gimp_progress_init("Assembling Jigsaw"); + if (jigsaw() == -1) + { + status = STATUS_CALLING_ERROR; + break; + } + gimp_set_data("plug_in_jigsaw", &config, sizeof(config_t)); + gimp_set_data(PLUG_IN_STORAGE, &globals.tooltips, + sizeof(globals.tooltips)); + gimp_displays_flush(); + break; + + case RUN_WITH_LAST_VALS: + gimp_get_data("plug_in_jigsaw", &config); + if (jigsaw() == -1) + { + status = STATUS_EXECUTION_ERROR; + gimp_message("An execution error occured."); + } + else + { + gimp_displays_flush(); + } + + } /* switch */ + + gimp_drawable_detach(drawable); + + *nreturn_vals = 1; + *return_vals = values; + values[0].type = PARAM_STATUS; + values[0].data.d_status = status; + + return; +} /* run */ + +/*****/ + +static gint +jigsaw(void) +{ + GPixelRgn src_pr, dest_pr; + guchar *buffer; + GDrawable *drawable = globals.drawable; + gint width = drawable->width; + gint height = drawable->height; + gint bytes = drawable->bpp; + gint buffer_size = bytes * width * height; + + srand((gint)NULL); + + + /* setup image buffer */ + gimp_pixel_rgn_init(&src_pr, drawable, 0, 0, width, height, FALSE, FALSE); + gimp_pixel_rgn_init(&dest_pr, drawable, 0, 0, width, height, TRUE, TRUE); + buffer = g_malloc(buffer_size); + gimp_pixel_rgn_get_rect(&src_pr, buffer, 0, 0, width, height); + + check_config(width, height); + globals.steps[LEFT] = globals.steps[RIGHT] = globals.steps[UP] + = globals.steps[DOWN] = (config.x < config.y) ? + (width / config.x * 2) : (height / config.y * 2); + + malloc_cache(); + draw_jigsaw(buffer, width, height, bytes); + free_cache(); + /* cleanup */ + gimp_pixel_rgn_set_rect(&dest_pr, buffer, 0, 0, width, height); + gimp_drawable_flush(drawable); + gimp_drawable_merge_shadow(drawable->id, TRUE); + gimp_drawable_update(drawable->id, 0, 0, width, height); + g_free(buffer); + + return 0; +} /* jigsaw */ + +/*****/ + +static void +generate_bezier(gint px[4], gint py[4], gint steps, + gint *cachex, gint *cachey) +{ + gdouble t = 0.0; + gdouble sigma = 1.0 / steps; + gint i; + + for (i = 0; i < steps; i++) + { + gdouble t2, t3, x, y, t_1; + + t += sigma; + t2 = t * t; + t3 = t2 * t; + t_1 = 1 - t; + x = t_1 * t_1 * t_1 * px[0] + + 3 * t * t_1 * t_1 * px[1] + + 3 * t2 * t_1 * px[2] + + t3 * px[3]; + y = t_1 * t_1 * t_1 * py[0] + + 3 * t * t_1 * t_1 * py[1] + + 3 * t2 * t_1 * py[2] + + t3 * py[3]; + cachex[i] = (gint) (x + 0.2); + cachey[i] = (gint) (y + 0.2); + } /* for */ + return; +} /* generate_bezier */ + +/*****/ + +static void +draw_jigsaw(guchar *buffer, gint width, gint height, gint bytes) +{ + gint i; + gint *x, *y; + gint xtiles = config.x; + gint ytiles = config.y; + gint xlines = xtiles - 1; + gint ylines = ytiles - 1; + gint blend_lines = config.blend_lines; + gdouble blend_amount = config.blend_amount; + gint steps = globals.steps[RIGHT]; + style_t style = config.style; + gint progress_total = xlines + ylines - 1; + + globals.gridx = g_malloc(sizeof(gint) * xtiles); + globals.gridy = g_malloc(sizeof(gint) * ytiles); + x = globals.gridx; + y = globals.gridy; + + generate_grid(width, height, xtiles, ytiles, globals.gridx, globals.gridy); + + init_right_bump(width, height); + init_left_bump(width, height); + init_up_bump(width, height); + init_down_bump(width, height); + + if (style == BEZIER_1) + { + for (i = 0; i < xlines; i++) + { + draw_vertical_border(buffer, width, height, bytes, x[i], ytiles, + blend_lines, blend_amount); + gimp_progress_update((gdouble) i / (gdouble) progress_total); + } + for (i = 0; i < ylines; i++) + { + draw_horizontal_border(buffer, width, bytes, y[i], xtiles, + blend_lines, blend_amount); + gimp_progress_update((gdouble) (i + xlines) + / (gdouble) progress_total); + } + } + else if (style == BEZIER_2) + { + for (i = 0; i < xlines; i++) + { + draw_bezier_vertical_border(buffer, width, height, bytes, x[i], + xtiles, ytiles, blend_lines, + blend_amount, steps); + gimp_progress_update((gdouble) i / (gdouble) progress_total); + } + for (i = 0; i < ylines; i++) + { + draw_bezier_horizontal_border(buffer, width, height, bytes, y[i], + xtiles, ytiles, blend_lines, + blend_amount, steps); + gimp_progress_update((gdouble) (i + xlines) + / (gdouble) progress_total); + } + } + else + { + printf("draw_jigsaw: bad style\n"); + exit(1); + } + g_free(globals.gridx); + g_free(globals.gridy); + + return; +} /* draw_jigsaw */ + +/*****/ + +static void +draw_vertical_border(guchar *buffer, gint width, gint height, gint bytes, + gint x_offset, gint ytiles, gint blend_lines, + gdouble blend_amount) +{ + gint i, j; + gint tile_height = height / ytiles; + gint tile_height_eighth = tile_height / 8; + gint curve_start_offset = 3 * tile_height_eighth; + gint curve_end_offset = curve_start_offset + 2 * tile_height_eighth; + gint px[2], py[2]; + gint ly[2], dy[2]; + gint y_offset = 0; + gdouble delta; + gdouble sigma = blend_amount / blend_lines; + gint right; + bump_t style_index; + + for (i = 0; i < ytiles; i++) + { + right = rand() & 1; + if (right) + { + style_index = RIGHT; + } + else + { + style_index = LEFT; + } + + /* first straight line from top downwards */ + px[0] = px[1] = x_offset; + py[0] = y_offset; py[1] = y_offset + curve_start_offset - 1; + draw_vertical_line(buffer, width, bytes, px, py); + delta = blend_amount; + dy[0] = ly[0] = py[0]; dy[1] = ly[1] = py[1]; + if (!right) + { + ly[1] += blend_lines + 2; + } + for (j = 0; j < blend_lines; j++) + { + dy[0]++; dy[1]--; ly[0]++; ly[1]--; + px[0] = x_offset - j - 1; + darken_vertical_line(buffer, width, bytes, px, dy, delta); + px[0] = x_offset + j + 1; + lighten_vertical_line(buffer, width, bytes, px, ly, delta); + delta -= sigma; + } + + /* top half of curve */ + if (right) + { + draw_right_bump(buffer, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[RIGHT]); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + /* use to be -j -1 */ + darken_right_bump(buffer, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[RIGHT], delta, j); + /* use to be +j + 1 */ + lighten_right_bump(buffer, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[RIGHT], delta, j); + delta -= sigma; + } + } + else + { + draw_left_bump(buffer, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[LEFT]); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + /* use to be -j -1 */ + darken_left_bump(buffer, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[LEFT], delta, j); + /* use to be -j - 1 */ + lighten_left_bump(buffer, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[LEFT], delta, j); + delta -= sigma; + } + } + /* bottom straight line of a tile wall */ + px[0] = px[1] = x_offset; + py[0] = y_offset + curve_end_offset; + py[1] = globals.gridy[i]; + draw_vertical_line(buffer, width, bytes, px, py); + delta = blend_amount; + dy[0] = ly[0] = py[0]; dy[1] = ly[1] = py[1]; + if (right) + { + dy[0] -= blend_lines + 2; + } + for (j = 0; j < blend_lines; j++) + { + dy[0]++; dy[1]--; ly[0]++; ly[1]--; + px[0] = x_offset - j - 1; + darken_vertical_line(buffer, width, bytes, px, dy, delta); + px[0] = x_offset + j + 1; + lighten_vertical_line(buffer, width, bytes, px, ly, delta); + delta -= sigma; + } + + y_offset = globals.gridy[i]; + } /* for */ + + return; +} /* draw_vertical_border */ + +/*****/ + +/* assumes RGB* */ +static void +draw_horizontal_border(guchar *buffer, gint width, gint bytes, + gint y_offset, gint xtiles, gint blend_lines, + gdouble blend_amount) +{ + gint i, j; + gint tile_width = width / xtiles; + gint tile_width_eighth = tile_width / 8; + gint curve_start_offset = 3 * tile_width_eighth; + gint curve_end_offset = curve_start_offset + 2 * tile_width_eighth; + gint px[2], py[2]; + gint dx[2], lx[2]; + gint x_offset = 0; + gdouble delta; + gdouble sigma = blend_amount / blend_lines; + gint up; + + for (i = 0; i < xtiles; i++) + { + up = rand() & 1; + /* first horizontal line across */ + px[0] = x_offset; px[1] = x_offset + curve_start_offset - 1; + py[0] = py[1] = y_offset; + draw_horizontal_line(buffer, width, bytes, px, py); + delta = blend_amount; + dx[0] = lx[0] = px[0]; dx[1] = lx[1] = px[1]; + if (up) + { + lx[1] += blend_lines + 2; + } + for (j = 0; j < blend_lines; j++) + { + dx[0]++; dx[1]--; lx[0]++; lx[1]--; + py[0] = y_offset - j - 1; + darken_horizontal_line(buffer, width, bytes, dx, py, delta); + py[0] = y_offset + j + 1; + lighten_horizontal_line(buffer, width, bytes, lx, py, delta); + delta -= sigma; + } + + if (up) + { + draw_up_bump(buffer, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[UP]); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + /* use to be -j -1 */ + darken_up_bump(buffer, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[UP], delta, j); + /* use to be +j + 1 */ + lighten_up_bump(buffer, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[UP], delta, j); + delta -= sigma; + } + } + else + { + draw_down_bump(buffer, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[DOWN]); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + /* use to be +j + 1 */ + darken_down_bump(buffer, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[DOWN], delta, j); + /* use to be -j -1 */ + lighten_down_bump(buffer, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[DOWN], delta, j); + delta -= sigma; + } + } + /* right horizontal line of tile */ + px[0] = x_offset + curve_end_offset; + px[1] = globals.gridx[i]; + py[0] = py[1] = y_offset; + draw_horizontal_line(buffer, width, bytes, px, py); + delta = blend_amount; + dx[0] = lx[0] = px[0]; dx[1] = lx[1] = px[1]; + if (!up) + { + dx[0] -= blend_lines + 2; + } + for (j = 0;j < blend_lines; j++) + { + dx[0]++; dx[1]--; lx[0]++; lx[1]--; + py[0] = y_offset - j - 1; + darken_horizontal_line(buffer, width, bytes, dx, py, delta); + py[0] = y_offset + j + 1; + lighten_horizontal_line(buffer, width, bytes, lx, py, delta); + delta -= sigma; + } + x_offset = globals.gridx[i]; + } /* for */ + + return; +} /* draw_horizontal_border */ + +/*****/ + +/* assumes going top to bottom */ +static void +draw_vertical_line(guchar *buffer, gint width, gint bytes, + gint px[2], gint py[2]) +{ + gint i; + gint rowstride = bytes * width; + gint index = px[0] * bytes + rowstride * py[0]; + gint length = py[1] - py[0] + 1; + + for (i = 0; i < length; i++) + { + DRAW_POINT(buffer, index); + index += rowstride; + } + return; +} /* draw_vertical_line */ + +/*****/ + +/* assumes going left to right */ +static void +draw_horizontal_line(guchar *buffer, gint width, gint bytes, + gint px[2], gint py[2]) +{ + gint i; + gint rowstride = bytes * width; + gint index = px[0] * bytes + py[0] * rowstride; + gint length = px[1] - px[0] + 1; + + for (i = 0; i < length; i++) + { + DRAW_POINT(buffer, index); + index += bytes; + } + return; +} /* draw_horizontal_line */ + +/*****/ + +static void +draw_right_bump(guchar *buffer, gint width, gint bytes, gint x_offset, + gint curve_start_offset, gint steps) +{ + gint i; + gint x, y; + gint index; + gint rowstride = width * bytes; + + for (i = 0; i < steps; i++) + { + x = *(globals.cachex1[RIGHT] + i) + x_offset; + y = *(globals.cachey1[RIGHT] + i) + curve_start_offset; + index = y * rowstride + x * bytes; + DRAW_POINT(buffer, index); + + x = *(globals.cachex2[RIGHT] + i) + x_offset; + y = *(globals.cachey2[RIGHT] + i) + curve_start_offset; + index = y * rowstride + x * bytes; + DRAW_POINT(buffer, index); + } + return; +} /* draw_right_bump */ + +/*****/ + +static void +draw_left_bump(guchar *buffer, gint width, gint bytes, gint x_offset, + gint curve_start_offset, gint steps) +{ + gint i; + gint x, y; + gint index; + gint rowstride = width * bytes; + + for (i = 0; i < steps; i++) + { + x = *(globals.cachex1[LEFT] + i) + x_offset; + y = *(globals.cachey1[LEFT] + i) + curve_start_offset; + index = y * rowstride + x * bytes; + DRAW_POINT(buffer, index); + + x = *(globals.cachex2[LEFT] + i) + x_offset; + y = *(globals.cachey2[LEFT] + i) + curve_start_offset; + index = y * rowstride + x * bytes; + DRAW_POINT(buffer, index); + } + return; +} /* draw_left_bump */ + +/*****/ + +static void +draw_up_bump(guchar *buffer, gint width, gint bytes, gint y_offset, + gint curve_start_offset, gint steps) +{ + gint i; + gint x, y; + gint index; + gint rowstride = width * bytes; + + for (i = 0; i < steps; i++) + { + x = *(globals.cachex1[UP] + i) + curve_start_offset; + y = *(globals.cachey1[UP] + i) + y_offset; + index = y * rowstride + x * bytes; + DRAW_POINT(buffer, index); + + x = *(globals.cachex2[UP] + i) + curve_start_offset; + y = *(globals.cachey2[UP] + i) + y_offset; + index = y * rowstride + x * bytes; + DRAW_POINT(buffer, index); + } + return; +} /* draw_up_bump */ + +/*****/ + +static void +draw_down_bump(guchar *buffer, gint width, gint bytes, gint y_offset, + gint curve_start_offset, gint steps) +{ + gint i; + gint x, y; + gint index; + gint rowstride = width * bytes; + + for (i = 0; i < steps; i++) + { + x = *(globals.cachex1[DOWN] + i) + curve_start_offset; + y = *(globals.cachey1[DOWN] + i) + y_offset; + index = y * rowstride + x * bytes; + DRAW_POINT(buffer, index); + + x = *(globals.cachex2[DOWN] + i) + curve_start_offset; + y = *(globals.cachey2[DOWN] + i) + y_offset; + index = y * rowstride + x * bytes; + DRAW_POINT(buffer, index); + } + return; +} /* draw_down_bump */ + +/*****/ + +static void +malloc_cache(void) +{ + gint i, j; + gint blend_lines = config.blend_lines; + gint length = blend_lines * sizeof(gint *); + + for (i = 0; i < 4; i++) + { + gint steps_length = globals.steps[i] * sizeof(gint); + + globals.cachex1[i] = g_malloc(steps_length); + globals.cachex2[i] = g_malloc(steps_length); + globals.cachey1[i] = g_malloc(steps_length); + globals.cachey2[i] = g_malloc(steps_length); + globals.blend_outer_cachex1[i] = g_malloc(length); + globals.blend_outer_cachex2[i] = g_malloc(length); + globals.blend_outer_cachey1[i] = g_malloc(length); + globals.blend_outer_cachey2[i] = g_malloc(length); + globals.blend_inner_cachex1[i] = g_malloc(length); + globals.blend_inner_cachex2[i] = g_malloc(length); + globals.blend_inner_cachey1[i] = g_malloc(length); + globals.blend_inner_cachey2[i] = g_malloc(length); + for (j = 0; j < blend_lines; j++) + { + *(globals.blend_outer_cachex1[i] + j) = g_malloc(steps_length); + *(globals.blend_outer_cachex2[i] + j) = g_malloc(steps_length); + *(globals.blend_outer_cachey1[i] + j) = g_malloc(steps_length); + *(globals.blend_outer_cachey2[i] + j) = g_malloc(steps_length); + *(globals.blend_inner_cachex1[i] + j) = g_malloc(steps_length); + *(globals.blend_inner_cachex2[i] + j) = g_malloc(steps_length); + *(globals.blend_inner_cachey1[i] + j) = g_malloc(steps_length); + *(globals.blend_inner_cachey2[i] + j) = g_malloc(steps_length); + } + } /* for */ + return; +} /* malloc_cache() */ + +/*****/ + +static void +free_cache(void) +{ + gint i, j; + gint blend_lines = config.blend_lines; + + for (i = 0; i < 4; i ++) + { + g_free(globals.cachex1[i]); + g_free(globals.cachex2[i]); + g_free(globals.cachey1[i]); + g_free(globals.cachey2[i]); + for (j = 0; j < blend_lines; j++) + { + g_free(*(globals.blend_outer_cachex1[i] + j)); + g_free(*(globals.blend_outer_cachex2[i] + j)); + g_free(*(globals.blend_outer_cachey1[i] + j)); + g_free(*(globals.blend_outer_cachey2[i] + j)); + g_free(*(globals.blend_inner_cachex1[i] + j)); + g_free(*(globals.blend_inner_cachex2[i] + j)); + g_free(*(globals.blend_inner_cachey1[i] + j)); + g_free(*(globals.blend_inner_cachey2[i] + j)); + } + g_free(globals.blend_outer_cachex1[i]); + g_free(globals.blend_outer_cachex2[i]); + g_free(globals.blend_outer_cachey1[i]); + g_free(globals.blend_outer_cachey2[i]); + g_free(globals.blend_inner_cachex1[i]); + g_free(globals.blend_inner_cachex2[i]); + g_free(globals.blend_inner_cachey1[i]); + g_free(globals.blend_inner_cachey2[i]); + } /* for */ + return; +} /* free_cache */ + +/*****/ + +static void +init_right_bump(gint width, gint height) +{ + gint i; + gint xtiles = config.x; + gint ytiles = config.y; + gint steps = globals.steps[RIGHT]; + gint px[4], py[4]; + gint x_offset = 0; + gint tile_width = width / xtiles; + gint tile_height = height/ ytiles; + gint tile_height_eighth = tile_height / 8; + gint curve_start_offset = 0; + gint curve_end_offset = curve_start_offset + 2 * tile_height_eighth; + gint blend_lines = config.blend_lines; + + px[0] = x_offset; + px[1] = x_offset + XFACTOR2 * tile_width; + px[2] = x_offset + XFACTOR3 * tile_width; + px[3] = x_offset + XFACTOR4 * tile_width; + py[0] = curve_start_offset; + py[1] = curve_start_offset + YFACTOR2 * tile_height; + py[2] = curve_start_offset - YFACTOR3 * tile_height; + py[3] = curve_start_offset + YFACTOR4 * tile_height; + generate_bezier(px, py, steps, globals.cachex1[RIGHT], + globals.cachey1[RIGHT]); + /* outside right bump */ + for (i = 0; i < blend_lines; i++) + { + py[0]--; py[1]--; py[2]--; px[3]++; + generate_bezier(px, py, steps, + *(globals.blend_outer_cachex1[RIGHT] + i), + *(globals.blend_outer_cachey1[RIGHT] + i)); + } + /* inside right bump */ + py[0] += blend_lines; py[1] += blend_lines; py[2] += blend_lines; + px[3] -= blend_lines; + for (i = 0; i < blend_lines; i++) + { + py[0]++; py[1]++; py[2]++; px[3]--; + generate_bezier(px, py, steps, + *(globals.blend_inner_cachex1[RIGHT] + i), + *(globals.blend_inner_cachey1[RIGHT] + i)); + } + + /* bottom half of bump */ + px[0] = x_offset + XFACTOR5 * tile_width; + px[1] = x_offset + XFACTOR6 * tile_width; + px[2] = x_offset + XFACTOR7 * tile_width; + px[3] = x_offset; + py[0] = curve_start_offset + YFACTOR5 * tile_height; + py[1] = curve_start_offset + YFACTOR6 * tile_height; + py[2] = curve_start_offset + YFACTOR7 * tile_height; + py[3] = curve_end_offset; + generate_bezier(px, py, steps, globals.cachex2[RIGHT], + globals.cachey2[RIGHT]); + /* outer right bump */ + for (i = 0; i < blend_lines; i++) + { + py[1]++; py[2]++; py[3]++; px[0]++; + generate_bezier(px, py, steps, + *(globals.blend_outer_cachex2[RIGHT] + i), + *(globals.blend_outer_cachey2[RIGHT] + i)); + } + /* inner right bump */ + py[1] -= blend_lines; py[2] -= blend_lines; py[3] -= blend_lines; + px[0] -= blend_lines; + for (i = 0; i < blend_lines; i++) + { + py[1]--; py[2]--; py[3]--; px[0]--; + generate_bezier(px, py, steps, + *(globals.blend_inner_cachex2[RIGHT] + i), + *(globals.blend_inner_cachey2[RIGHT] + i)); + } + return; +} /* init_right_bump */ + +/*****/ + +static void +init_left_bump(gint width, gint height) +{ + gint i; + gint xtiles = config.x; + gint ytiles = config.y; + gint steps = globals.steps[LEFT]; + gint px[4], py[4]; + gint x_offset = 0; + gint tile_width = width / xtiles; + gint tile_height = height / ytiles; + gint tile_height_eighth = tile_height / 8; + gint curve_start_offset = 0; + gint curve_end_offset = curve_start_offset + 2 * tile_height_eighth; + gint blend_lines = config.blend_lines; + + px[0] = x_offset; + px[1] = x_offset - XFACTOR2 * tile_width; + px[2] = x_offset - XFACTOR3 * tile_width; + px[3] = x_offset - XFACTOR4 * tile_width; + py[0] = curve_start_offset; + py[1] = curve_start_offset + YFACTOR2 * tile_height; + py[2] = curve_start_offset - YFACTOR3 * tile_height; + py[3] = curve_start_offset + YFACTOR4 * tile_height; + generate_bezier(px, py, steps, globals.cachex1[LEFT], + globals.cachey1[LEFT]); + /* outer left bump */ + for (i = 0; i < blend_lines; i++) + { + py[0]--; py[1]--; py[2]--; px[3]--; + generate_bezier(px, py, steps, + *(globals.blend_outer_cachex1[LEFT] + i), + *(globals.blend_outer_cachey1[LEFT] + i)); + } + /* inner left bump */ + py[0] += blend_lines; py[1] += blend_lines; py[2] += blend_lines; + px[3] += blend_lines; + for (i = 0; i < blend_lines; i++) + { + py[0]++; py[1]++; py[2]++; px[3]++; + generate_bezier(px, py, steps, + *(globals.blend_inner_cachex1[LEFT] + i), + *(globals.blend_inner_cachey1[LEFT] + i)); + } + + /* bottom half of bump */ + px[0] = x_offset - XFACTOR5 * tile_width; + px[1] = x_offset - XFACTOR6 * tile_width; + px[2] = x_offset - XFACTOR7 * tile_width; + px[3] = x_offset; + py[0] = curve_start_offset + YFACTOR5 * tile_height; + py[1] = curve_start_offset + YFACTOR6 * tile_height; + py[2] = curve_start_offset + YFACTOR7 * tile_height; + py[3] = curve_end_offset; + generate_bezier(px, py, steps, globals.cachex2[LEFT], + globals.cachey2[LEFT]); + /* outer left bump */ + for (i = 0; i < blend_lines; i++) + { + py[1]++; py[2]++; py[3]++; px[0]--; + generate_bezier(px, py, steps, + *(globals.blend_outer_cachex2[LEFT] + i), + *(globals.blend_outer_cachey2[LEFT] + i)); + } + /* inner left bump */ + py[1] -= blend_lines; py[2] -= blend_lines; py[3] -= blend_lines; + px[0] += blend_lines; + for (i = 0; i < blend_lines; i++) + { + py[1]--; py[2]--; py[3]--; px[0]++; + generate_bezier(px, py, steps, + *(globals.blend_inner_cachex2[LEFT] + i), + *(globals.blend_inner_cachey2[LEFT] + i)); + } + return; +} /* init_left_bump */ + +/*****/ + +static void +init_up_bump(gint width, gint height) +{ + gint i; + gint xtiles = config.x; + gint ytiles = config.y; + gint steps = globals.steps[UP]; + gint px[4], py[4]; + gint y_offset = 0; + gint tile_width = width / xtiles; + gint tile_height = height/ ytiles; + gint tile_width_eighth = tile_width / 8; + gint curve_start_offset = 0; + gint curve_end_offset = curve_start_offset + 2 * tile_width_eighth; + gint blend_lines = config.blend_lines; + + px[0] = curve_start_offset; + px[1] = curve_start_offset + YFACTOR2 * tile_width; + px[2] = curve_start_offset - YFACTOR3 * tile_width; + px[3] = curve_start_offset + YFACTOR4 * tile_width; + py[0] = y_offset; + py[1] = y_offset - XFACTOR2 * tile_height; + py[2] = y_offset - XFACTOR3 * tile_height; + py[3] = y_offset - XFACTOR4 * tile_height; + generate_bezier(px, py, steps, globals.cachex1[UP], + globals.cachey1[UP]); + /* outer up bump */ + for (i = 0; i < blend_lines; i++) + { + px[0]--; px[1]--; px[2]--; py[3]--; + generate_bezier(px, py, steps, + *(globals.blend_outer_cachex1[UP] + i), + *(globals.blend_outer_cachey1[UP] + i)); + } + /* inner up bump */ + px[0] += blend_lines; px[1] += blend_lines; px[2] += blend_lines; + py[3] += blend_lines; + for (i = 0; i < blend_lines; i++) + { + px[0]++; px[1]++; px[2]++; py[3]++; + generate_bezier(px, py, steps, + *(globals.blend_inner_cachex1[UP] + i), + *(globals.blend_inner_cachey1[UP] + i)); + } + + /* bottom half of bump */ + px[0] = curve_start_offset + YFACTOR5 * tile_width; + px[1] = curve_start_offset + YFACTOR6 * tile_width; + px[2] = curve_start_offset + YFACTOR7 * tile_width; + px[3] = curve_end_offset; + py[0] = y_offset - XFACTOR5 * tile_height; + py[1] = y_offset - XFACTOR6 * tile_height; + py[2] = y_offset - XFACTOR7 * tile_height; + py[3] = y_offset; + generate_bezier(px, py, steps, globals.cachex2[UP], + globals.cachey2[UP]); + /* outer up bump */ + for (i = 0; i < blend_lines; i++) + { + px[1]++; px[2]++; px[3]++; py[0]--; + generate_bezier(px, py, steps, + *(globals.blend_outer_cachex2[UP] + i), + *(globals.blend_outer_cachey2[UP] + i)); + } + /* inner up bump */ + px[1] -= blend_lines; px[2] -= blend_lines; px[3] -= blend_lines; + py[0] += blend_lines; + for (i = 0; i < blend_lines; i++) + { + px[1]--; px[2]--; px[3]--; py[0]++; + generate_bezier(px, py, steps, + *(globals.blend_inner_cachex2[UP] + i), + *(globals.blend_inner_cachey2[UP] + i)); + } + return; +} /* init_top_bump */ + +/*****/ + +static void +init_down_bump(gint width, gint height) +{ + gint i; + gint xtiles = config.x; + gint ytiles = config.y; + gint steps = globals.steps[DOWN]; + gint px[4], py[4]; + gint y_offset = 0; + gint tile_width = width / xtiles; + gint tile_height = height/ ytiles; + gint tile_width_eighth = tile_width / 8; + gint curve_start_offset = 0; + gint curve_end_offset = curve_start_offset + 2 * tile_width_eighth; + gint blend_lines = config.blend_lines; + + px[0] = curve_start_offset; + px[1] = curve_start_offset + YFACTOR2 * tile_width; + px[2] = curve_start_offset - YFACTOR3 * tile_width; + px[3] = curve_start_offset + YFACTOR4 * tile_width; + py[0] = y_offset; + py[1] = y_offset + XFACTOR2 * tile_height; + py[2] = y_offset + XFACTOR3 * tile_height; + py[3] = y_offset + XFACTOR4 * tile_height; + generate_bezier(px, py, steps, globals.cachex1[DOWN], + globals.cachey1[DOWN]); + /* outer down bump */ + for (i = 0; i < blend_lines; i++) + { + px[0]--; px[1]--; px[2]--; py[3]++; + generate_bezier(px, py, steps, + *(globals.blend_outer_cachex1[DOWN] + i), + *(globals.blend_outer_cachey1[DOWN] + i)); + } + /* inner down bump */ + px[0] += blend_lines; px[1] += blend_lines; px[2] += blend_lines; + py[3] -= blend_lines; + for (i = 0; i < blend_lines; i++) + { + px[0]++; px[1]++; px[2]++; py[3]--; + generate_bezier(px, py, steps, + *(globals.blend_inner_cachex1[DOWN] + i), + *(globals.blend_inner_cachey1[DOWN] + i)); + } + + /* bottom half of bump */ + px[0] = curve_start_offset + YFACTOR5 * tile_width; + px[1] = curve_start_offset + YFACTOR6 * tile_width; + px[2] = curve_start_offset + YFACTOR7 * tile_width; + px[3] = curve_end_offset; + py[0] = y_offset + XFACTOR5 * tile_height; + py[1] = y_offset + XFACTOR6 * tile_height; + py[2] = y_offset + XFACTOR7 * tile_height; + py[3] = y_offset; + generate_bezier(px, py, steps, globals.cachex2[DOWN], + globals.cachey2[DOWN]); + /* outer down bump */ + for (i = 0; i < blend_lines; i++) + { + px[1]++; px[2]++; px[3]++; py[0]++; + generate_bezier(px, py, steps, + *(globals.blend_outer_cachex2[DOWN] + i), + *(globals.blend_outer_cachey2[DOWN] + i)); + } + /* inner down bump */ + px[1] -= blend_lines; px[2] -= blend_lines; px[3] -= blend_lines; + py[0] -= blend_lines; + for (i = 0; i < blend_lines; i++) + { + px[1]--; px[2]--; px[3]--; py[0]--; + generate_bezier(px, py, steps, + *(globals.blend_inner_cachex2[DOWN] + i), + *(globals.blend_inner_cachey2[DOWN] + i)); + } + return; +} /* init_down_bump */ + +/*****/ + +static void +generate_grid(gint width, gint height, gint xtiles, gint ytiles, + gint *x, gint *y) +{ + gint i; + gint xlines = xtiles - 1; + gint ylines = ytiles - 1; + gint tile_width = width / xtiles; + gint tile_height = height / ytiles; + gint tile_width_leftover = width % xtiles; + gint tile_height_leftover = height % ytiles; + gint x_offset = tile_width; + gint y_offset = tile_height; + gint carry; + + for (i = 0; i < xlines; i++) + { + x[i] = x_offset; + x_offset += tile_width; + } + carry = 0; + while (tile_width_leftover) + { + for (i = carry; i < xlines; i++) + { + x[i] += 1; + } + tile_width_leftover--; + carry++; + } + x[xlines] = width - 1; /* padding for draw_horizontal_border */ + + for (i = 0; i < ytiles; i++) + { + y[i] = y_offset; + y_offset += tile_height; + } + carry = 0; + while (tile_height_leftover) + { + for (i = carry; i < ylines; i++) + { + y[i] += 1; + } + tile_height_leftover--; + carry++; + } + y[ylines] = height - 1; /* padding for draw_vertical_border */ + return; +} /* generate_grid */ + +/*****/ + +/* assumes RGB* */ +/* assumes py[1] > py[0] and px[0] = px[1] */ +static void +darken_vertical_line(guchar *buffer, gint width, gint bytes, + gint px[2], gint py[2], gdouble delta ) +{ + gint i; + gint rowstride = width * bytes; + gint index = px[0] * bytes + py[0] * rowstride; + gint length = py[1] - py[0] + 1; + gint temp; + + for (i = 0; i < length; i++) + { + DARKEN_POINT(buffer, index, delta, temp); + index += rowstride; + } + return; +} /* darken_vertical_line */ + +/*****/ + +/* assumes RGB* */ +/* assumes py[1] > py[0] and px[0] = px[1] */ +static void +lighten_vertical_line(guchar *buffer, gint width, gint bytes, + gint px[2], gint py[2], gdouble delta) +{ + gint i; + gint rowstride = width * bytes; + gint index = px[0] * bytes + py[0] * rowstride; + gint length = py[1] - py[0] + 1; + gint temp; + + for (i = 0; i < length; i++) + { + LIGHTEN_POINT(buffer, index, delta, temp); + index += rowstride; + } + return; +} /* lighten_vertical_line */ + +/*****/ + +/* assumes RGB* */ +/* assumes px[1] > px[0] and py[0] = py[1] */ +static void +darken_horizontal_line(guchar *buffer, gint width, gint bytes, + gint px[2], gint py[2], gdouble delta) +{ + gint i; + gint rowstride = width * bytes; + gint index = px[0] * bytes + py[0] * rowstride; + gint length = px[1] - px[0] + 1; + gint temp; + + for (i = 0; i < length; i++) + { + DARKEN_POINT(buffer, index, delta, temp); + index += bytes; + } + return; +} /* darken_horizontal_line */ + +/*****/ + +/* assumes RGB* */ +/* assumes px[1] > px[0] and py[0] = py[1] */ +static void +lighten_horizontal_line(guchar *buffer, gint width, gint bytes, + gint px[2], gint py[2], gdouble delta) +{ + gint i; + gint rowstride = width * bytes; + gint index = px[0] * bytes + py[0] * rowstride; + gint length = px[1] - px[0] + 1; + gint temp; + + for (i = 0; i < length; i++) + { + LIGHTEN_POINT(buffer, index, delta, temp); + index += bytes; + } + return; +} /* lighten_horizontal_line */ + +/*****/ + +static void +darken_right_bump(guchar *buffer, gint width, gint bytes, gint x_offset, + gint curve_start_offset, gint steps, gdouble delta, + gint counter) +{ + gint i; + gint x, y; + gint index; + gint last_index1 = -1; + gint last_index2 = -1; + gint rowstride = width * bytes; + gint temp; + gint j = counter; + + for (i = 0; i < steps; i++) + { + x = x_offset + + *(*(globals.blend_inner_cachex1[RIGHT] + j) + i); + y = curve_start_offset + + *(*(globals.blend_inner_cachey1[RIGHT] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index1) + { + if (i < steps / 1.3) + { + LIGHTEN_POINT(buffer, index, delta, temp); + } + else + { + DARKEN_POINT(buffer, index, delta, temp); + } + last_index1 = index; + } + + x = x_offset + + *(*(globals.blend_inner_cachex2[RIGHT] + j) + i); + y = curve_start_offset + + *(*(globals.blend_inner_cachey2[RIGHT] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index2) + { + DARKEN_POINT(buffer, index, delta, temp); + last_index2 = index; + } + } + return; +} /* darken_right_bump */ + +/*****/ + +static void +lighten_right_bump(guchar *buffer, gint width, gint bytes, gint x_offset, + gint curve_start_offset, gint steps, gdouble delta, + gint counter) +{ + gint i; + gint x, y; + gint index; + gint last_index1 = -1; + gint last_index2 = -1; + gint rowstride = width * bytes; + gint temp; + gint j = counter; + + for (i = 0; i < steps; i++) + { + x = x_offset + + *(*(globals.blend_outer_cachex1[RIGHT] + j) + i); + y = curve_start_offset + + *(*(globals.blend_outer_cachey1[RIGHT] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index1) + { + if (i < steps / 1.3) + { + DARKEN_POINT(buffer, index, delta, temp); + } + else + { + LIGHTEN_POINT(buffer, index, delta, temp); + } + last_index1 = index; + } + + x = x_offset + + *(*(globals.blend_outer_cachex2[RIGHT] + j) + i); + y = curve_start_offset + + *(*(globals.blend_outer_cachey2[RIGHT] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index2) + { + LIGHTEN_POINT(buffer, index, delta, temp); + last_index2 = index; + } + } + return; +} /* lighten_right_bump */ + +/*****/ + +static void +darken_left_bump(guchar *buffer, gint width, gint bytes, gint x_offset, + gint curve_start_offset, gint steps, gdouble delta, + gint counter) +{ + gint i; + gint x, y; + gint index; + gint last_index1 = -1; + gint last_index2 = -1; + gint rowstride = width * bytes; + gint temp; + gint j = counter; + + for (i = 0; i < steps; i++) + { + x = x_offset + + *(*(globals.blend_outer_cachex1[LEFT] + j) + i); + y = curve_start_offset + + *(*(globals.blend_outer_cachey1[LEFT] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index1) + { + DARKEN_POINT(buffer, index, delta, temp); + last_index1 = index; + } + + x = x_offset + + *(*(globals.blend_outer_cachex2[LEFT] + j) + i); + y = curve_start_offset + + *(*(globals.blend_outer_cachey2[LEFT] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index2) + { + if (i < steps / 4) + { + DARKEN_POINT(buffer, index, delta, temp); + } + else + { + LIGHTEN_POINT(buffer, index, delta, temp); + } + last_index2 = index; + } + } + return; +} /* darken_left_bump */ + +/*****/ + +static void +lighten_left_bump(guchar *buffer, gint width, gint bytes, gint x_offset, + gint curve_start_offset, gint steps, gdouble delta, + gint counter) +{ + gint i; + gint x, y; + gint index; + gint last_index1 = -1; + gint last_index2 = -1; + gint rowstride = width * bytes; + gint temp; + gint j = counter; + + for (i = 0; i < steps; i++) + { + x = x_offset + + *(*(globals.blend_inner_cachex1[LEFT] + j) + i); + y = curve_start_offset + + *(*(globals.blend_inner_cachey1[LEFT] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index1) + { + LIGHTEN_POINT(buffer, index, delta, temp); + last_index1 = index; + } + + x = x_offset + + *(*(globals.blend_inner_cachex2[LEFT] + j) + i); + y = curve_start_offset + + *(*(globals.blend_inner_cachey2[LEFT] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index2) + { + if (i < steps / 4) + { + LIGHTEN_POINT(buffer, index, delta, temp); + } + else + { + DARKEN_POINT(buffer, index, delta, temp); + } + last_index2 = index; + } + } + return; +} /* lighten_left_bump */ + +/*****/ + +static void +darken_up_bump(guchar *buffer, gint width, gint bytes, gint y_offset, + gint curve_start_offset, gint steps, gdouble delta, + gint counter) +{ + gint i; + gint x, y; + gint index; + gint last_index1 = -1; + gint last_index2 = -1; + gint rowstride = width * bytes; + gint temp; + gint j = counter; + + for (i = 0; i < steps; i++) + { + x = curve_start_offset + + *(*(globals.blend_outer_cachex1[UP] + j) + i); + y = y_offset + + *(*(globals.blend_outer_cachey1[UP] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index1) + { + DARKEN_POINT(buffer, index, delta, temp); + last_index1 = index; + } + + x = curve_start_offset + + *(*(globals.blend_outer_cachex2[UP] + j) + i); + y = y_offset + + *(*(globals.blend_outer_cachey2[UP] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index2) + { + if (i < steps / 4) + { + DARKEN_POINT(buffer, index, delta, temp); + } + else + { + LIGHTEN_POINT(buffer, index, delta, temp); + } + last_index2 = index; + } + } + return; +} /* darken_up_bump */ + +/*****/ + +static void +lighten_up_bump(guchar *buffer, gint width, gint bytes, gint y_offset, + gint curve_start_offset, gint steps, gdouble delta, + gint counter) +{ + gint i; + gint x, y; + gint index; + gint last_index1 = -1; + gint last_index2 = -1; + gint rowstride = width * bytes; + gint temp; + gint j = counter; + + for (i = 0; i < steps; i++) + { + x = curve_start_offset + + *(*(globals.blend_inner_cachex1[UP] + j) + i); + y = y_offset + + *(*(globals.blend_inner_cachey1[UP] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index1) + { + LIGHTEN_POINT(buffer, index, delta, temp); + last_index1 = index; + } + + x = curve_start_offset + + *(*(globals.blend_inner_cachex2[UP] + j) + i); + y = y_offset + + *(*(globals.blend_inner_cachey2[UP] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index2) + { + if (i < steps / 4) + { + LIGHTEN_POINT(buffer, index, delta, temp); + } + else + { + DARKEN_POINT(buffer, index, delta, temp); + } + last_index2 = index; + } + } + return; +} /* lighten_up_bump */ + +/*****/ + +static void +darken_down_bump(guchar *buffer, gint width, gint bytes, gint y_offset, + gint curve_start_offset, gint steps, gdouble delta, + gint counter) +{ + gint i; + gint x, y; + gint index; + gint last_index1 = -1; + gint last_index2 = -1; + gint rowstride = width * bytes; + gint temp; + gint j = counter; + + for (i = 0; i < steps; i++) + { + x = curve_start_offset + + *(*(globals.blend_inner_cachex1[DOWN] + j) + i); + y = y_offset + + *(*(globals.blend_inner_cachey1[DOWN] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index1) + { + if (i < steps / 1.2) + { + LIGHTEN_POINT(buffer, index, delta, temp); + } + else + { + DARKEN_POINT(buffer, index, delta, temp); + } + last_index1 = index; + } + + x = curve_start_offset + + *(*(globals.blend_inner_cachex2[DOWN] + j) + i); + y = y_offset + + *(*(globals.blend_inner_cachey2[DOWN] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index2) + { + DARKEN_POINT(buffer, index, delta, temp); + last_index2 = index; + } + } + return; +} /* darken_down_bump */ + +/*****/ + +static void +lighten_down_bump(guchar *buffer, gint width, gint bytes, gint y_offset, + gint curve_start_offset, gint steps, gdouble delta, + gint counter) +{ + gint i; + gint x, y; + gint index; + gint last_index1 = -1; + gint last_index2 = -1; + gint rowstride = width * bytes; + gint temp; + gint j = counter; + + for (i = 0; i < steps; i++) + { + x = curve_start_offset + + *(*(globals.blend_outer_cachex1[DOWN] + j) + i); + y = y_offset + + *(*(globals.blend_outer_cachey1[DOWN] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index1) + { + if (i < steps / 1.2) + { + DARKEN_POINT(buffer, index, delta, temp); + } + else + { + LIGHTEN_POINT(buffer, index, delta, temp); + } + last_index1 = index; + } + + x = curve_start_offset + + *(*(globals.blend_outer_cachex2[DOWN] + j) + i); + y = y_offset + + *(*(globals.blend_outer_cachey2[DOWN] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index2) + { + LIGHTEN_POINT(buffer, index, delta, temp); + last_index2 = index; + } + } + return; +} /* lighten_down_bump */ + +/*****/ + +static void +draw_bezier_line(guchar *buffer, gint width, gint bytes, + gint steps, gint *cx, gint *cy) +{ + gint i; + gint x, y; + gint index; + gint rowstride = width * bytes; + + for (i = 0; i < steps; i++) + { + x = cx[i]; + y = cy[i]; + index = y * rowstride + x * bytes; + DRAW_POINT(buffer, index); + } + return; +} /* draw_bezier_line */ + +/*****/ + +static void +darken_bezier_line(guchar *buffer, gint width, gint bytes, + gint x_offset, gint y_offset, gint steps, + gint *cx, gint *cy, gdouble delta) +{ + gint i; + gint x, y; + gint index; + gint last_index = -1; + gint rowstride = width * bytes; + gint temp; + + for (i = 0; i < steps; i++) + { + x = cx[i] + x_offset; + y = cy[i] + y_offset; + index = y * rowstride + x * bytes; + if (index != last_index) + { + DARKEN_POINT(buffer, index, delta, temp); + last_index = index; + } + } + return; +} /* darken_bezier_line */ + +/*****/ + +static void +lighten_bezier_line(guchar *buffer, gint width, gint bytes, + gint x_offset, gint y_offset, gint steps, + gint *cx, gint *cy, gdouble delta) +{ + gint i; + gint x, y; + gint index; + gint last_index = -1; + gint rowstride = width * bytes; + gint temp; + + for (i = 0; i < steps; i++) + { + x = cx[i] + x_offset; + y = cy[i] + y_offset; + index = y * rowstride + x * bytes; + if (index != last_index) + { + LIGHTEN_POINT(buffer, index, delta, temp); + last_index = index; + } + } + return; +} /* lighten_bezier_line */ + +/*****/ + +static void +draw_bezier_vertical_border(guchar *buffer, gint width, gint height, + gint bytes, gint x_offset, gint xtiles, + gint ytiles, gint blend_lines, + gdouble blend_amount, gint steps) +{ + gint i, j; + gint tile_width = width / xtiles; + gint tile_height = height / ytiles; + gint tile_height_eighth = tile_height / 8; + gint curve_start_offset = 3 * tile_height_eighth; + gint curve_end_offset = curve_start_offset + 2 * tile_height_eighth; + gint px[4], py[4]; + gint y_offset = 0; + gdouble delta; + gdouble sigma = blend_amount / blend_lines; + gint right; + bump_t style_index; + gint *cachex, *cachey; + + cachex = g_malloc(steps * sizeof(gint)); + cachey = g_malloc(steps * sizeof(gint)); + + for (i = 0; i < ytiles; i++) + { + right = rand() & 1; + if (right) + { + style_index = RIGHT; + } + else + { + style_index = LEFT; + } + px[0] = px[3] = x_offset; + px[1] = x_offset + WALL_XFACTOR2 * tile_width * FUDGE; + px[2] = x_offset + WALL_XFACTOR3 * tile_width * FUDGE; + py[0] = y_offset; + py[1] = y_offset + WALL_YCONS2 * tile_height; + py[2] = y_offset + WALL_YCONS3 * tile_height; + py[3] = y_offset + curve_start_offset; + + if (right) + { + px[1] = x_offset - WALL_XFACTOR2 * tile_width; + px[2] = x_offset - WALL_XFACTOR3 * tile_width; + } + generate_bezier(px, py, steps, cachex, cachey); + draw_bezier_line(buffer, width, bytes, steps, cachex, cachey); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + px[0] = -j - 1; + darken_bezier_line(buffer, width, bytes, px[0], 0, + steps, cachex, cachey, delta); + px[0] = j + 1; + lighten_bezier_line(buffer, width, bytes, px[0], 0, + steps, cachex, cachey, delta); + delta -= sigma; + } + if (right) + { + draw_right_bump(buffer, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[RIGHT]); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + /* use to be -j -1 */ + darken_right_bump(buffer, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[RIGHT], delta, j); + /* use to be +j + 1 */ + lighten_right_bump(buffer, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[RIGHT], delta, j); + delta -= sigma; + } + } + else + { + draw_left_bump(buffer, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[LEFT]); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + /* use to be -j -1 */ + darken_left_bump(buffer, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[LEFT], delta, j); + /* use to be -j - 1 */ + lighten_left_bump(buffer, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[LEFT], delta, j); + delta -= sigma; + } + } + px[0] = px[3] = x_offset; + px[1] = x_offset + WALL_XFACTOR2 * tile_width * FUDGE; + px[2] = x_offset + WALL_XFACTOR3 * tile_width * FUDGE; + py[0] = y_offset + curve_end_offset; + py[1] = y_offset + curve_end_offset + WALL_YCONS2 * tile_height; + py[2] = y_offset + curve_end_offset + WALL_YCONS3 * tile_height; + py[3] = globals.gridy[i]; + if (right) + { + px[1] = x_offset - WALL_XFACTOR2 * tile_width; + px[2] = x_offset - WALL_XFACTOR3 * tile_width; + } + generate_bezier(px, py, steps, cachex, cachey); + draw_bezier_line(buffer, width, bytes, steps, cachex, cachey); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + px[0] = -j - 1; + darken_bezier_line(buffer, width, bytes, px[0], 0, + steps, cachex, cachey, delta); + px[0] = j + 1; + lighten_bezier_line(buffer, width, bytes, px[0], 0, + steps, cachex, cachey, delta); + delta -= sigma; + } + y_offset = globals.gridy[i]; + } /* for */ + g_free(cachex); + g_free(cachey); + + return; +} /* draw_bezier_vertical_border */ + +/*****/ + +static void +draw_bezier_horizontal_border(guchar *buffer, gint width, gint height, + gint bytes, gint y_offset, gint xtiles, + gint ytiles, gint blend_lines, + gdouble blend_amount, gint steps) +{ + gint i, j; + gint tile_width = width / xtiles; + gint tile_height = height / ytiles; + gint tile_width_eighth = tile_width / 8; + gint curve_start_offset = 3 * tile_width_eighth; + gint curve_end_offset = curve_start_offset + 2 * tile_width_eighth; + gint px[4], py[4]; + gint x_offset = 0; + gdouble delta; + gdouble sigma = blend_amount / blend_lines; + gint up; + style_t style_index; + gint *cachex, *cachey; + + cachex = g_malloc(steps * sizeof(gint)); + cachey = g_malloc(steps * sizeof(gint)); + + for (i = 0; i < xtiles; i++) + { + up = rand() & 1; + if (up) + { + style_index = UP; + } + else + { + style_index = DOWN; + } + px[0] = x_offset; + px[1] = x_offset + WALL_XCONS2 * tile_width; + px[2] = x_offset + WALL_XCONS3 * tile_width; + px[3] = x_offset + curve_start_offset; + py[0] = py[3] = y_offset; + py[1] = y_offset + WALL_YFACTOR2 * tile_height * FUDGE; + py[2] = y_offset + WALL_YFACTOR3 * tile_height * FUDGE; + if (!up) + { + py[1] = y_offset - WALL_YFACTOR2 * tile_height; + py[2] = y_offset - WALL_YFACTOR3 * tile_height; + } + generate_bezier(px, py, steps, cachex, cachey); + draw_bezier_line(buffer, width, bytes, steps, cachex, cachey); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + py[0] = -j - 1; + darken_bezier_line(buffer, width, bytes, 0, py[0], + steps, cachex, cachey, delta); + py[0] = j + 1; + lighten_bezier_line(buffer, width, bytes, 0, py[0], + steps, cachex, cachey, delta); + delta -= sigma; + } + /* bumps */ + if (up) + { + draw_up_bump(buffer, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[UP]); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + /* use to be -j -1 */ + darken_up_bump(buffer, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[UP], delta, j); + /* use to be +j + 1 */ + lighten_up_bump(buffer, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[UP], delta, j); + delta -= sigma; + } + } + else + { + draw_down_bump(buffer, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[DOWN]); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + /* use to be +j + 1 */ + darken_down_bump(buffer, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[DOWN], delta, j); + /* use to be -j -1 */ + lighten_down_bump(buffer, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[DOWN], delta, j); + delta -= sigma; + } + } + /* ending side wall line */ + px[0] = x_offset + curve_end_offset; + px[1] = x_offset + curve_end_offset + WALL_XCONS2 * tile_width; + px[2] = x_offset + curve_end_offset + WALL_XCONS3 * tile_width; + px[3] = globals.gridx[i]; + py[0] = py[3] = y_offset; + py[1] = y_offset + WALL_YFACTOR2 * tile_height * FUDGE; + py[2] = y_offset + WALL_YFACTOR3 * tile_height * FUDGE; + if (!up) + { + py[1] = y_offset - WALL_YFACTOR2 * tile_height; + py[2] = y_offset - WALL_YFACTOR3 * tile_height; + } + generate_bezier(px, py, steps, cachex, cachey); + draw_bezier_line(buffer, width, bytes, steps, cachex, cachey); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + py[0] = -j - 1; + darken_bezier_line(buffer, width, bytes, 0, py[0], + steps, cachex, cachey, delta); + py[0] = j + 1; + lighten_bezier_line(buffer, width, bytes, 0, py[0], + steps, cachex, cachey, delta); + delta -= sigma; + } + x_offset = globals.gridx[i]; + } /* for */ + g_free(cachex); + g_free(cachey); + + return; +} /* draw_bezier_horizontal_border */ + +/*****/ + +static void +check_config(gint width, gint height) +{ + gint tile_width, tile_height; + gint tile_width_limit, tile_height_limit; + + if (config.x < 1) + { + config.x = 1; + } + if (config.y < 1) + { + config.y = 1; + } + if (config.blend_amount < 0) + { + config.blend_amount = 0; + } + if (config.blend_amount > 5) + { + config.blend_amount = 5; + } + tile_width = width / config.x; + tile_height = height / config.y; + tile_width_limit = 0.4 * tile_width; + tile_height_limit = 0.4 * tile_height; + if ((config.blend_lines > tile_width_limit) + || (config.blend_lines > tile_height_limit)) + { + config.blend_lines = MIN(tile_width_limit, tile_height_limit); + } + return; +} /*check_config */ + +/*****/ + +/******************************************************** + GUI +********************************************************/ + +static void +dialog_box(void) +{ + GtkWidget *dlg; + GtkWidget *button; + GtkWidget *rbutton; + GtkWidget *cbutton; + GSList *list; + GtkWidget *hbox; + GtkWidget *frame; + GtkWidget *label; + GtkWidget *entry; + GtkWidget *table; + GtkWidget *scale; + GtkObject *adjustment; + GtkTooltips *tooltips; + GdkColor tips_fg, tips_bg; + + gchar buffer[12]; + + gchar **argv; + gint argc; + + argc = 1; + argv = g_new(gchar *, 1); + argv[0] = g_strdup(PLUG_IN_NAME); + + gtk_init(&argc, &argv); + gtk_rc_parse(gimp_gtkrc()); + + /* Create the dialog box */ + + dlg = gtk_dialog_new(); + gtk_window_set_title(GTK_WINDOW(dlg), PLUG_IN_NAME); + gtk_window_position(GTK_WINDOW(dlg), GTK_WIN_POS_MOUSE); + gtk_signal_connect(GTK_OBJECT(dlg), "destroy", + (GtkSignalFunc) dialog_close_callback, NULL); + + /* Action Area */ + + button = gtk_button_new_with_label("OK"); + GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); + gtk_signal_connect(GTK_OBJECT(button), "clicked", + (GtkSignalFunc) run_callback, dlg); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->action_area), button, + TRUE, TRUE, 0); + gtk_widget_grab_default(button); + gtk_widget_show(button); + + button = gtk_button_new_with_label("Cancel"); + GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); + gtk_signal_connect_object(GTK_OBJECT(button), "clicked", + (GtkSignalFunc) gtk_widget_destroy, + GTK_OBJECT(dlg)); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->action_area), button, + TRUE, TRUE, 0); + gtk_widget_show(button); + + /* init tooltips */ + tooltips = gtk_tooltips_new(); + tips_fg.red = 0; + tips_fg.green = 0; + tips_fg.blue = 0; + gdk_color_alloc(gtk_widget_get_colormap(dlg), &tips_fg); + tips_bg.red = 61669; + tips_bg.green = 59113; + tips_bg.blue = 35979; + gdk_color_alloc(gtk_widget_get_colormap(dlg), &tips_bg); + gtk_tooltips_set_colors(tooltips, &tips_bg, &tips_fg); + if (globals.tooltips == 0) + { + gtk_tooltips_disable(tooltips); + } + /* paramters frame */ + frame = gtk_frame_new("Number of Tiles"); + gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN); + gtk_container_border_width(GTK_CONTAINER(frame), 10); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->vbox), frame, TRUE, TRUE, 0); + + table = gtk_table_new(3, 2, FALSE); + gtk_container_border_width (GTK_CONTAINER(table), 10); + gtk_container_add(GTK_CONTAINER(frame), table); + + /* xtiles */ + label = gtk_label_new("Horizontal:"); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 5, 0); + gtk_widget_show(label); + + adjustment = gtk_adjustment_new(config.x, MIN_XTILES, MAX_XTILES, + 1.0, 1.0, 0); + gtk_signal_connect(GTK_OBJECT(adjustment), "value_changed", + (GtkSignalFunc) adjustment_callback, + &config.x); + scale = gtk_hscale_new(GTK_ADJUSTMENT(adjustment)); + gtk_widget_set_usize(scale, SCALE_WIDTH, 0); + gtk_table_attach(GTK_TABLE(table), scale, 1, 2, 0, 1, GTK_FILL, 0, 0, 0); + gtk_scale_set_draw_value(GTK_SCALE(scale), FALSE); + gtk_range_set_update_policy(GTK_RANGE(scale), GTK_UPDATE_CONTINUOUS); + gtk_widget_show(scale); + gtk_tooltips_set_tip(tooltips, scale, XTILES_TEXT, NULL); + + entry = gtk_entry_new(); + gtk_object_set_user_data(GTK_OBJECT(entry), adjustment); + gtk_object_set_user_data(GTK_OBJECT(adjustment), entry); + gtk_widget_set_usize(entry, ENTRY_WIDTH, 0); + sprintf(buffer, "%i", config.x); + gtk_entry_set_text(GTK_ENTRY(entry), buffer); + gtk_signal_connect(GTK_OBJECT(entry), "changed", + GTK_SIGNAL_FUNC(entry_callback), + (gpointer) &config.x); + gtk_table_attach(GTK_TABLE(table), entry, 2, 3, 0, 1, GTK_FILL, 0, 0, 0); + gtk_widget_show(entry); + gtk_tooltips_set_tip(tooltips, entry, XTILES_TEXT, NULL); + + /* ytiles */ + label = gtk_label_new("Vertical:"); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, GTK_FILL, 0, 5, 0); + gtk_widget_show(label); + + adjustment = gtk_adjustment_new(config.y, MIN_YTILES, MAX_YTILES, + 1.0, 1.0, 0 ); + gtk_signal_connect(GTK_OBJECT(adjustment), "value_changed", + (GtkSignalFunc) adjustment_callback, + &config.y); + scale = gtk_hscale_new(GTK_ADJUSTMENT(adjustment)); + gtk_widget_set_usize(scale, SCALE_WIDTH, 0); + gtk_table_attach(GTK_TABLE(table), scale, 1, 2, 1, 2, GTK_FILL, 0, 0, 0); + gtk_scale_set_draw_value(GTK_SCALE(scale), FALSE); + gtk_range_set_update_policy(GTK_RANGE(scale), GTK_UPDATE_CONTINUOUS); + gtk_widget_show(scale); + gtk_tooltips_set_tip(tooltips, scale, YTILES_TEXT, NULL); + + entry = gtk_entry_new(); + gtk_object_set_user_data(GTK_OBJECT(entry), adjustment); + gtk_object_set_user_data(GTK_OBJECT(adjustment), entry); + gtk_widget_set_usize(entry, ENTRY_WIDTH, 0); + sprintf(buffer, "%i", config.y); + gtk_entry_set_text(GTK_ENTRY(entry), buffer); + gtk_signal_connect(GTK_OBJECT(entry), "changed", + GTK_SIGNAL_FUNC(entry_callback), + (gpointer) &config.y); + gtk_table_attach(GTK_TABLE(table), entry, 2, 3, 1, 2, GTK_FILL, 0, 0, 0); + gtk_widget_show(entry); + gtk_tooltips_set_tip(tooltips, entry, YTILES_TEXT, NULL); + + gtk_widget_show(table); + gtk_widget_show(frame); + + /* frame for bevel blending */ + + frame = gtk_frame_new("Bevel Edges"); + gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN); + gtk_container_border_width(GTK_CONTAINER(frame), 10); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->vbox), frame, TRUE, TRUE, 0); + + table = gtk_table_new(3, 2, FALSE); + gtk_container_border_width(GTK_CONTAINER(table), 10); + gtk_container_add(GTK_CONTAINER(frame), table); + + /* number of blending lines */ + label = gtk_label_new("Bevel width:"); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 5, 0); + gtk_widget_show(label); + + adjustment = gtk_adjustment_new(config.blend_lines, MIN_BLEND_LINES, + MAX_BLEND_LINES, 1.0, 1.0, 0); + gtk_signal_connect(GTK_OBJECT(adjustment), "value_changed", + (GtkSignalFunc) adjustment_callback, + &config.blend_lines); + scale = gtk_hscale_new(GTK_ADJUSTMENT(adjustment)); + gtk_widget_set_usize(scale, SCALE_WIDTH, 0); + gtk_table_attach(GTK_TABLE(table), scale, 1, 2, 0, 1, GTK_FILL, 0, 0, 0); + gtk_scale_set_draw_value(GTK_SCALE(scale), FALSE); + gtk_range_set_update_policy(GTK_RANGE(scale), GTK_UPDATE_CONTINUOUS); + gtk_widget_show(scale); + gtk_tooltips_set_tip(tooltips, scale, BLEND_LINES_TEXT, NULL); + + entry = gtk_entry_new(); + gtk_object_set_user_data(GTK_OBJECT(entry), adjustment); + gtk_object_set_user_data(GTK_OBJECT(adjustment), entry); + gtk_widget_set_usize(entry, ENTRY_WIDTH, 0); + sprintf(buffer, "%i", config.blend_lines); + gtk_entry_set_text(GTK_ENTRY(entry), buffer); + gtk_signal_connect(GTK_OBJECT(entry), "changed", + GTK_SIGNAL_FUNC(entry_callback), + (gpointer) &config.blend_lines); + gtk_table_attach(GTK_TABLE(table), entry, 2, 3, 0, 1, GTK_FILL, 0, 0, 0); + gtk_widget_show(entry); + gtk_tooltips_set_tip(tooltips, entry, BLEND_LINES_TEXT, NULL); + + /* blending amount */ + label = gtk_label_new("Highlight:"); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, GTK_FILL, 0, 5, 0); + gtk_widget_show(label); + + adjustment = gtk_adjustment_new(config.blend_amount, MIN_BLEND_AMOUNT, + MAX_BLEND_AMOUNT, 1.0, 0.05, 0.0); + gtk_signal_connect(GTK_OBJECT(adjustment), "value_changed", + (GtkSignalFunc) adjustment_double_callback, + &config.blend_amount); + scale = gtk_hscale_new(GTK_ADJUSTMENT(adjustment)); + gtk_widget_set_usize(scale, SCALE_WIDTH, 0); + gtk_table_attach(GTK_TABLE(table), scale, 1, 2, 1, 2, GTK_FILL, 0, 0, 0); + gtk_scale_set_draw_value(GTK_SCALE(scale), FALSE); + gtk_range_set_update_policy(GTK_RANGE(scale), GTK_UPDATE_CONTINUOUS); + gtk_widget_show(scale); + gtk_tooltips_set_tip(tooltips, scale, BLEND_AMOUNT_TEXT, NULL); + + entry = gtk_entry_new(); + gtk_object_set_user_data(GTK_OBJECT(entry), adjustment); + gtk_object_set_user_data(GTK_OBJECT(adjustment), entry); + gtk_widget_set_usize(entry, ENTRY_WIDTH, 0); + sprintf(buffer, "%0.2f", config.blend_amount); + gtk_entry_set_text(GTK_ENTRY(entry), buffer); + gtk_signal_connect(GTK_OBJECT(entry), "changed", + GTK_SIGNAL_FUNC(entry_double_callback), + (gpointer) &config.blend_amount); + gtk_table_attach(GTK_TABLE(table), entry, 2, 3, 1, 2, GTK_FILL, 0, 0, 0); + gtk_widget_show(entry); + gtk_tooltips_set_tip(tooltips, entry, BLEND_AMOUNT_TEXT, NULL); + + gtk_widget_show(table); + gtk_widget_show(frame); + + /* frame for primitive radio buttons */ + + hbox = gtk_hbox_new(FALSE, 5); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->vbox), hbox, FALSE, FALSE, 0); + + frame = gtk_frame_new("Jigsaw Style"); + gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN); + gtk_container_border_width(GTK_CONTAINER(frame), 10); + gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 0); + + table = gtk_table_new(2, 1, FALSE); + gtk_container_border_width(GTK_CONTAINER(table), 5); + gtk_container_add(GTK_CONTAINER(frame), table); + + rbutton = gtk_radio_button_new_with_label(NULL, "Square"); + list = gtk_radio_button_group((GtkRadioButton *) rbutton); + gtk_toggle_button_set_state((GtkToggleButton *) rbutton, + config.style == BEZIER_1 ? TRUE : FALSE); + gtk_signal_connect(GTK_OBJECT(rbutton), "toggled", + GTK_SIGNAL_FUNC(radio_button_primitive_callback), + (gpointer) BEZIER_1); + gtk_table_attach(GTK_TABLE(table), rbutton, 0, 1, 0, 1, GTK_FILL, 0, 10, 0); + gtk_widget_show(rbutton); + gtk_tooltips_set_tip(tooltips, rbutton, SQUARE_TEXT, NULL); + + rbutton = gtk_radio_button_new_with_label(list, "Curved"); + list = gtk_radio_button_group((GtkRadioButton *) rbutton); + gtk_toggle_button_set_state((GtkToggleButton *) rbutton, + config.style == BEZIER_2 ? TRUE : FALSE); + gtk_signal_connect(GTK_OBJECT(rbutton), "toggled", + GTK_SIGNAL_FUNC(radio_button_primitive_callback), + (gpointer) BEZIER_2); + gtk_table_attach(GTK_TABLE(table), rbutton, 1, 2, 0, 1, GTK_FILL, 0, 10, 0); + gtk_widget_show(rbutton); + gtk_tooltips_set_tip(tooltips, rbutton, CURVE_TEXT, NULL); + + gtk_widget_show(table); + gtk_widget_show(frame); + + table = gtk_table_new(1, 3, FALSE); + gtk_container_border_width(GTK_CONTAINER(table), 3); + gtk_box_pack_start(GTK_BOX(hbox), table, TRUE, TRUE, 0); + + cbutton = gtk_check_button_new_with_label("Disable Tooltips"); + gtk_toggle_button_set_state((GtkToggleButton *) cbutton, + globals.tooltips ? FALSE : TRUE); + gtk_signal_connect(GTK_OBJECT(cbutton), "toggled", + (GtkSignalFunc) check_button_callback, + (gpointer) tooltips); + gtk_table_attach(GTK_TABLE(table), cbutton, 0, 1, 1, 2, 0, 0, 0, 20); + gtk_widget_show(cbutton); + gtk_tooltips_set_tip(tooltips, cbutton, DISABLE_TEXT, NULL); + + gtk_widget_show(table); + gtk_widget_show(hbox); + gtk_widget_show(dlg); + + gtk_main(); + gdk_flush(); + + return; +} + +/*************************************************** + callbacks + ***************************************************/ + +static void +run_callback(GtkWidget *widget, gpointer data) +{ + globals.dialog_result = 1; + gtk_widget_destroy(GTK_WIDGET(data)); + return; +} + +/*****/ + +static void +dialog_close_callback(GtkWidget *widget, gpointer data) +{ + gtk_main_quit(); + return; +} /* dialog_close_callback */ + +/*****/ + +static void +entry_callback(GtkWidget *widget, gpointer data) +{ + GtkAdjustment *adjustment; + gint new_value; + + new_value = atoi(gtk_entry_get_text(GTK_ENTRY(widget))); + + if (*(gint *)data != new_value) + { + *(gint *)data = new_value; + adjustment = gtk_object_get_user_data(GTK_OBJECT(widget)); + if ((new_value >= adjustment->lower) + && (new_value <= adjustment->upper)) + { + adjustment->value = new_value; + gtk_signal_handler_block_by_data(GTK_OBJECT(adjustment), data); + gtk_signal_emit_by_name(GTK_OBJECT(adjustment), "value_changed"); + gtk_signal_handler_unblock_by_data(GTK_OBJECT(adjustment), data); + } + } + return; +} /* entry_callback */ + +/*****/ + +static void +radio_button_primitive_callback(GtkWidget *widget, gpointer data) +{ + if (GTK_TOGGLE_BUTTON (widget)->active) /* button just got checked */ + { + if (data == (gpointer) BEZIER_1) + { + config.style = BEZIER_1; + } + else if (data == (gpointer) BEZIER_2) + { + config.style = BEZIER_2; + } + else + { + printf("radio_button_callback: bad data\n"); + } + } + return; +} /* radio_button_primitive_callback */ + +/*****/ + +static void +check_button_callback(GtkWidget *widget, gpointer data) +{ + if (GTK_TOGGLE_BUTTON (widget)->active) + { + gtk_tooltips_disable((GtkTooltips *) data); + globals.tooltips = 0; + } + else + { + gtk_tooltips_enable((GtkTooltips *) data); + globals.tooltips = 1; + } + return; +} /* check_button_callback */ + +/*****/ + +static void +adjustment_callback(GtkAdjustment *adjustment, gpointer data) +{ + GtkWidget *entry; + gchar buffer[50]; + + if (*(gint *)data != adjustment->value) + { + *(gint *)data = adjustment->value; + entry = gtk_object_get_user_data(GTK_OBJECT(adjustment)); + sprintf(buffer, "%d", *(gint *)data); + + gtk_signal_handler_block_by_data(GTK_OBJECT(entry), data); + gtk_entry_set_text(GTK_ENTRY(entry), buffer); + gtk_signal_handler_unblock_by_data(GTK_OBJECT(entry), data); + } + return; +} /* adjustment_callback */ + +/*****/ + +static void +adjustment_double_callback(GtkAdjustment *adjustment, gpointer data) +{ + GtkWidget *entry; + gchar buffer[50]; + + if (*(gdouble *)data != adjustment->value) + { + *(gdouble *)data = adjustment->value; + entry = gtk_object_get_user_data(GTK_OBJECT(adjustment)); + sprintf(buffer, "%0.2f", *(gdouble *)data); + + gtk_signal_handler_block_by_data(GTK_OBJECT(entry), data); + gtk_entry_set_text(GTK_ENTRY(entry), buffer); + gtk_signal_handler_unblock_by_data(GTK_OBJECT(entry), data); + } + return; +} /* adjustment_double_callback */ + +/*****/ + +static void +entry_double_callback(GtkWidget *widget, gpointer data) +{ + GtkAdjustment *adjustment; + gdouble new_value; + + new_value = atof(gtk_entry_get_text(GTK_ENTRY(widget))); + + if (*(gdouble *)data != new_value) + { + *(gdouble *)data = new_value; + adjustment = gtk_object_get_user_data(GTK_OBJECT(widget)); + if ((new_value >= adjustment->lower) + && (new_value <= adjustment->upper)) + { + adjustment->value = new_value; + gtk_signal_handler_block_by_data(GTK_OBJECT(adjustment), data); + gtk_signal_emit_by_name(GTK_OBJECT(adjustment), "value_changed"); + gtk_signal_handler_unblock_by_data(GTK_OBJECT(adjustment), data); + } + } + return; +} /* entry_double_callback */ diff --git a/plug-ins/common/newsprint.c b/plug-ins/common/newsprint.c new file mode 100644 index 0000000000..904f27828e --- /dev/null +++ b/plug-ins/common/newsprint.c @@ -0,0 +1,2237 @@ +/* The GIMP -- an image manipulation program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * newsprint plug-in + * Copyright (C) 1997-1998 Austin Donnelly + * + * 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. + * + * Portions of this plug-in are copyright 1991-1992 Adobe Systems + * Incorporated. See the spot_PS*() functions for details. + */ + +/* + * version 0.01 + * This version requires gtk-1.0.4 or above. + * + * This plug-in puts an image through a screen at a particular angle + * and lines per inch, to arrive at a newspaper-style effect. + * + * Austin Donnelly + * http://www.cl.cam.ac.uk/~and1000/newsprint/ + * + * Richard Mortier did the spot_round() function + * with correct tonal balance. + * + * Tim Harris provided valuable feedback on + * pre-press issues. + * + */ + + +#include +#include +#include +#include +#include "libgimp/gimp.h" + +#ifdef RCSID +static char rcsid[] = "$Id$"; +#endif + +#define VERSION "v0.01" + +/* Some useful macros */ +#ifdef DEBUG +#define DEBUG_PRINT(x) printf x +#else +#define DEBUG_PRINT(x) +#endif + +/* HACK so we compile with old gtks. Won't work on machines where the + * size of an int is different from the size of a void*, eg Alphas */ +#ifndef GINT_TO_POINTER +#warning glib did not define GINT_TO_POINTER, assuming same size as int +#define GINT_TO_POINTER(x) ((gpointer)(x)) +#endif + +#ifndef GPOINTER_TO_INT +#warning glib did not define GPOINTER_TO_INT, assuming same size as int +#define GPOINTER_TO_INT(x) ((int)(x)) +#endif + + +#define TIMINGS + +#ifdef TIMINGS +#include +#include +#define TM_INIT() struct timeval tm_start, tm_end +#define TM_START() gettimeofday(&tm_start, NULL) +#define TM_END() \ +do { \ + gettimeofday(&tm_end, NULL); \ + tm_end.tv_sec -= tm_start.tv_sec; \ + tm_end.tv_usec -= tm_start.tv_usec; \ + if (tm_end.tv_usec < 0) \ + { \ + tm_end.tv_usec += 1000000; \ + tm_end.tv_sec -= 1; \ + } \ + printf("operation took %ld.%06ld\n", tm_end.tv_sec, tm_end.tv_usec); \ +} while(0) +#else +#define TM_INIT() +#define TM_START() +#define TM_END() +#endif + +#define TILE_CACHE_SIZE 16 +#define ENTSCALE_SCALE_WIDTH 125 +#define ENTSCALE_ENTRY_WIDTH 40 +#define DEF_OVERSAMPLE 1 /* default for how much to oversample by */ +#define SPOT_PREVIEW_SZ 16 +#define PREVIEW_OVERSAMPLE 3 + + +#define BOUNDS(a,x,y) ((a < x) ? x : ((a > y) ? y : a)) +#define ISNEG(x) (((x) < 0)? 1 : 0) +#define DEG2RAD(d) ((d) * M_PI / 180) +#define VALID_BOOL(x) ((x) == TRUE || (x) == FALSE) +#define CLAMPED_ADD(a, b) (((a)+(b) > 0xff)? 0xff : (a) + (b)) + +/* Ideally, this would be in a common header file somewhere. This was + * nicked from app/convert.c */ +#define INTENSITY(r,g,b) (r * 0.30 + g * 0.59 + b * 0.11 + 0.001) + +/* Bartlett window supersampling weight function. See table 4.1, page + * 123 of Alan Watt and Mark Watt, Advanced Animation and Rendering + * Techniques, theory and practice. Addison-Wesley, 1992. ISBN + * 0-201-54412-1 */ +#define BARTLETT(x,y) (((oversample/2)+1-ABS(x)) * ((oversample/2)+1-ABS(y))) +#define WGT(x,y) wgt[((y+oversample/2)*oversample) + x+oversample/2] + +/* colourspaces we can separate to: */ +#define CS_GREY 0 +#define CS_RGB 1 +#define CS_CMYK 2 +#define CS_INTENSITY 3 +#define NUM_CS 4 +#define VALID_CS(x) ((x) >= 0 && (x) <= NUM_CS-1) + +/* Spot function related types and definitions */ + +typedef gdouble spotfn_t (gdouble x, gdouble y); + +/* forward declaration of the functions themselves */ +static spotfn_t spot_round; +static spotfn_t spot_line; +static spotfn_t spot_diamond; +static spotfn_t spot_PSsquare; +static spotfn_t spot_PSdiamond; + +typedef struct { + const char *name; /* function's name */ + spotfn_t *fn; /* function itself */ + guchar *thresh; /* cached threshold matrix */ + gdouble prev_lvl[3]; /* intensities to preview */ + guchar *prev_thresh; /* preview-sized threshold matrix */ + gint balanced; /* TRUE if spot fn is already balanced */ +} spot_info_t; + + +/* This is all the info needed per spot function. Functions are refered to + * by their index into this array. */ +static spot_info_t spotfn_list[] = { +#define SPOTFN_DOT 0 + {"round", + spot_round, + NULL, + {.22, .50, .90}, + NULL, + FALSE}, + + {"line", + spot_line, + NULL, + {.15, .50, .80}, + NULL, + FALSE}, + + {"diamond", + spot_diamond, + NULL, + {.15, .50, .80}, + NULL, + TRUE}, + + {"PS square (Euclidean dot)", + spot_PSsquare, + NULL, + {.15, .50, .90}, + NULL, + FALSE}, + + {"PS diamond", + spot_PSdiamond, + NULL, + {.15, .50, .90}, + NULL, + FALSE}, + + /* NULL-name terminates */ + {NULL, + NULL, + NULL, + {0.0, 0.0, 0.0}, + NULL, + FALSE} +}; + +#define NUM_SPOTFN ((sizeof(spotfn_list) / sizeof(spot_info_t)) - 1) +#define VALID_SPOTFN(x) ((x) >= 0 && (x) < NUM_SPOTFN) +#define THRESH(x,y) (thresh[(y)*width + (x)]) +#define THRESHn(n,x,y) ((thresh[n])[(y)*width + (x)]) + + + +/* Arguments to filter */ + +/* Some of these are here merely to save them across calls. They are + * marked as "UI use". Everything else is a valid arg. */ +typedef struct { + /* resolution section: */ + gint cell_width; + + /* screening section: */ + gint colourspace; /* 0: RGB, 1: CMYK, 2: Intensity */ + gint k_pullout; /* percentage of black to pull out */ + + /* grey screen (only used if greyscale drawable) */ + gdouble gry_ang; + gint gry_spotfn; + /* red / cyan screen */ + gdouble red_ang; + gint red_spotfn; + /* green / magenta screen */ + gdouble grn_ang; + gint grn_spotfn; + /* blue / yellow screen */ + gdouble blu_ang; + gint blu_spotfn; + + /* anti-alias section */ + gint oversample; /* 1 == no anti-aliasing, else small odd int */ +} NewsprintValues; + +/* bits of state used by the UI, but not visible from the PDB */ +typedef struct { + gint input_spi; /* input samples per inch */ + gdouble output_lpi; /* desired output lines per inch */ + gint lock_channels; /* changes to one channel affect all */ +} NewsprintUIValues; + +typedef struct { + gint run; +} NewsprintInterface; + + +typedef void (*EntscaleCallbackFunc) (gdouble new_val, gpointer data); + + +typedef enum { + ENTSCALE_INT, + ENTSCALE_DOUBLE +} EntscaleType; + + +typedef struct { + GtkObject *adjustment; + GtkWidget *entry; + GtkWidget *label; + GtkWidget *hbox; + EntscaleType type; + gchar fmt_string[16]; + gint constraint; + EntscaleCallbackFunc callback; + gpointer call_data; +} Entscale; + + + + +/* state for the preview widgets */ +typedef struct { + GtkWidget *widget; /* preview widget itself */ + GtkWidget *label; /* the label below it */ +} preview_st; + +/* state for the channel frames */ +typedef struct _channel_st { + GtkWidget *frame; /* frame around this channel */ + gint *spotfn_num; /* which spotfn the menu is controlling */ + preview_st prev[3]; /* state for 3 preview widgets */ + Entscale *entscale; /* angle entscale widget */ + GtkWidget *option_menu; /* popup for spot function */ + GtkWidget *menuitem[NUM_SPOTFN]; /* menuitems for each spot function */ + struct _channel_st *next; /* circular list of channels in locked group */ +} channel_st; + + +/* State associated with the configuration dialog and passed to its + * callback functions */ +typedef struct { + GtkWidget *dlg; /* main dialog itself */ + Entscale *pull; /* black pullout percentage */ + GtkWidget *vbox; /* container for screen info */ + /* room for up to 4 channels per colourspace */ + channel_st *chst[NUM_CS][4]; +} NewsprintDialog_st; + + + + +/***** Local vars *****/ + + +/* defaults */ +/* fixed copy so we can reset them */ +static const NewsprintValues factory_defaults = +{ + /* resolution stuff */ + 10, /* cell width */ + + /* screen setup (default is the classic rosette pattern) */ + CS_RGB, /* use RGB, not CMYK or Intensity */ + 100, /* max pullout */ + /* grey/black */ + 45.0, + SPOTFN_DOT, + /* red/cyan */ + 15.0, + SPOTFN_DOT, + /* green/magenta */ + 75.0, + SPOTFN_DOT, + /* blue/yellow */ + 0.0, + SPOTFN_DOT, + + /* anti-alias control */ + DEF_OVERSAMPLE +}; + +static const NewsprintUIValues factory_defaults_ui = { + 75, /* input spi */ + 7.5, /* output lpi */ + FALSE /* lock channels */ +}; + +/* Mutable copy for normal use. Initialised in run(). */ +static NewsprintValues pvals; +static NewsprintUIValues pvals_ui; + +static NewsprintInterface pint = +{ + FALSE /* run */ +}; + + +/* channel templates */ +typedef struct { + const gchar *name; + /* pointers to the variables this channel updates */ + gdouble *angle; + gint *spotfn; + /* factory defaults for the angle and spot function (as pointers so + * the silly compiler can see they're really constants) */ + const gdouble *factory_angle; + const gint *factory_spotfn; +} chan_tmpl; + +static const chan_tmpl grey_tmpl[] = { + {"Grey", + &pvals.gry_ang, + &pvals.gry_spotfn, + &factory_defaults.gry_ang, + &factory_defaults.gry_spotfn}, + + {NULL, NULL, NULL, NULL, NULL} +}; + +static const chan_tmpl rgb_tmpl[] = { + {"Red", + &pvals.red_ang, + &pvals.red_spotfn, + &factory_defaults.red_ang, + &factory_defaults.red_spotfn}, + + {"Green", + &pvals.grn_ang, + &pvals.grn_spotfn, + &factory_defaults.grn_ang, + &factory_defaults.grn_spotfn}, + + {"Blue", + &pvals.blu_ang, + &pvals.blu_spotfn, + &factory_defaults.blu_ang, + &factory_defaults.blu_spotfn}, + + {NULL, NULL, NULL, NULL, NULL} +}; + +static const chan_tmpl cmyk_tmpl[] = { + {"Cyan", + &pvals.red_ang, + &pvals.red_spotfn, + &factory_defaults.red_ang, + &factory_defaults.red_spotfn}, + + + {"Magenta", + &pvals.grn_ang, + &pvals.grn_spotfn, + &factory_defaults.grn_ang, + &factory_defaults.grn_spotfn}, + + {"Yellow", + &pvals.blu_ang, + &pvals.blu_spotfn, + &factory_defaults.blu_ang, + &factory_defaults.blu_spotfn}, + + {"Black", + &pvals.gry_ang, + &pvals.gry_spotfn, + &factory_defaults.gry_ang, + &factory_defaults.gry_spotfn}, + + {NULL, NULL, NULL, NULL, NULL} +}; + +static const chan_tmpl intensity_tmpl[] = { + {"Intensity", + &pvals.gry_ang, + &pvals.gry_spotfn, + &factory_defaults.gry_ang, + &factory_defaults.gry_spotfn}, + + {NULL, NULL, NULL, NULL, NULL} +}; + +/* cspace_chan_tmpl is indexed by colourspace, and gives an array of + * channel templates for that colourspace */ +static const chan_tmpl *cspace_chan_tmpl[] = { + grey_tmpl, + rgb_tmpl, + cmyk_tmpl, + intensity_tmpl +}; + +#define NCHANS(x) ((sizeof(x) / sizeof(chan_tmpl)) - 1) + +/* cspace_nchans gives a quick way of finding the number of channels + * in a colourspace. Alternatively, if you're walking the channel + * template, you can use the NULL entry at the end to stop. */ +static const gint cspace_nchans[] = { + NCHANS(grey_tmpl), + NCHANS(rgb_tmpl), + NCHANS(cmyk_tmpl), + NCHANS(intensity_tmpl) +}; + + +/* Declare local functions. */ +static void query (void); +static void run (gchar *name, + gint nparams, + GParam *param, + gint *nreturn_vals, + GParam **return_vals); + +static gint newsprint_dialog (GDrawable *drawable); +static void newsprint_close_callback (GtkWidget *widget, + gpointer data); +static void newsprint_ok_callback (GtkWidget *widget, + gpointer data); + +static void newsprint_toggle_update (GtkWidget *widget, + gpointer data); + +static void newsprint_cspace_update (GtkWidget *widget, + gpointer data); + +static void newsprint (GDrawable *drawable); + +static guchar * spot2thresh (gint type, gint width); + +static Entscale * entscale_new (GtkWidget *table, gint x, gint y, + gchar *caption, EntscaleType type, gpointer variable, + gdouble min, gdouble max, gdouble step, + gint constraint, EntscaleCallbackFunc callback, + gpointer call_data); + + +static int entscale_get_precision (gdouble step); +static void entscale_destroy_callback (GtkWidget *widget, gpointer data); +static void entscale_scale_update (GtkAdjustment *adjustment, gpointer data); +static void entscale_entry_update (GtkWidget *widget, gpointer data); + +static void entscale_set_sensitive (Entscale *ent, gint sensitive); +static void entscale_set_value(Entscale *ent, gfloat value); + + + +GPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run /* run_proc */ +}; + + + +/***** Functions *****/ + +MAIN () + +static void +query() +{ + static GParamDef args[]= + { + { PARAM_INT32, "run_mode", "Interactive, non-interactive" }, + { PARAM_IMAGE, "image", "Input image (unused)" }, + { PARAM_DRAWABLE, "drawable", "Input drawable" }, + + { PARAM_INT32, "cell_width", "screen cell width, in pixels" }, + + { PARAM_INT32, "colourspace", "separate to 0:RGB, 1:CMYK, 2:Intensity" }, + { PARAM_INT32, "k_pullout", "Percentage of black to pullout (CMYK only)" }, + + { PARAM_FLOAT, "gry_ang", "Grey/black screen angle (degrees)" }, + { PARAM_INT32, "gry_spotfn", "Grey/black spot function (0=dots, 1=lines, 2=diamonds, 3=euclidean dot, 4=PS diamond)" }, + { PARAM_FLOAT, "red_ang", "Red/cyan screen angle (degrees)" }, + { PARAM_INT32, "red_spotfn", "Red/cyan spot function (values as gry_spotfn)" }, + { PARAM_FLOAT, "grn_ang", "Green/magenta screen angle (degrees)" }, + { PARAM_INT32, "grn_spotfn", "Green/magenta spot function (values as gry_spotfn)" }, + { PARAM_FLOAT, "blu_ang", "Blue/yellow screen angle (degrees)" }, + { PARAM_INT32, "blu_spotfn", "Blue/yellow spot function (values as gry_spotfn)" }, + + { PARAM_INT32, "oversample", "how many times to oversample spot fn" } + /* 15 args */ + }; + static GParamDef *return_vals = NULL; + static gint nargs = sizeof (args) / sizeof (args[0]); + static gint nreturn_vals = 0; + + gimp_install_procedure ("plug_in_newsprint", + "Re-sample the image to give a newspaper-like " + "effect", + "Halftone the image, trading off " + "resolution to represent colours or grey levels " + "using the process described both in the PostScript " + "language definition, and also by Robert Ulichney, " + "\"Digital halftoning\", MIT Press, 1987.", + "Austin Donnelly", + "Austin Donnelly", + "1998 (" VERSION ")", + "/Filters/Misc/Newsprint", + "RGB*, GRAY*", + PROC_PLUG_IN, + nargs, nreturn_vals, + args, return_vals); +} + +static void +run (gchar *name, + gint nparams, + GParam *param, + gint *nreturn_vals, + GParam **return_vals) +{ + static GParam values[1]; + GDrawable *drawable; + GRunModeType run_mode; + GStatusType status = STATUS_SUCCESS; + + run_mode = param[0].data.d_int32; + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = PARAM_STATUS; + values[0].data.d_status = status; + + /* basic defaults */ + pvals = factory_defaults; + pvals_ui = factory_defaults_ui; + + /* Get the specified drawable */ + drawable = gimp_drawable_get (param[2].data.d_drawable); + + switch (run_mode) + { + case RUN_INTERACTIVE: + /* Possibly retrieve data */ + gimp_get_data ("plug_in_newsprint", &pvals); + gimp_get_data ("plug_in_newsprint_ui", &pvals_ui); + + /* First acquire information with a dialog */ + if (! newsprint_dialog (drawable)) + { + gimp_drawable_detach (drawable); + return; + } + break; + + case RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + if (nparams != 15) + { + status = STATUS_CALLING_ERROR; + break; + } + + pvals.cell_width = param[3].data.d_int32; + pvals.colourspace = param[4].data.d_int32; + pvals.k_pullout = param[5].data.d_int32; + pvals.gry_ang = param[6].data.d_float; + pvals.gry_spotfn = param[7].data.d_int32; + pvals.red_ang = param[8].data.d_float; + pvals.red_spotfn = param[9].data.d_int32; + pvals.grn_ang = param[10].data.d_float; + pvals.grn_spotfn = param[11].data.d_int32; + pvals.blu_ang = param[12].data.d_float; + pvals.blu_spotfn = param[13].data.d_int32; + pvals.oversample = param[14].data.d_int32; + + /* check values are within permitted ranges */ + if (!VALID_SPOTFN(pvals.gry_spotfn) || + !VALID_SPOTFN(pvals.red_spotfn) || + !VALID_SPOTFN(pvals.grn_spotfn) || + !VALID_SPOTFN(pvals.blu_spotfn) || + !VALID_CS(pvals.colourspace) || + pvals.k_pullout < 0 || pvals.k_pullout > 100) + status = STATUS_CALLING_ERROR; + + break; + + case RUN_WITH_LAST_VALS: + /* Possibly retrieve data */ + gimp_get_data ("plug_in_newsprint", &pvals); + break; + + default: + break; + } + + if (status == STATUS_SUCCESS) + { + /* Make sure that the drawable is gray or RGB color */ + if (gimp_drawable_color (drawable->id) || + gimp_drawable_gray (drawable->id)) + { + gimp_progress_init("Newsprintifing..."); + + /* set the tile cache size */ + gimp_tile_cache_ntiles(TILE_CACHE_SIZE); + + /* run the newsprint effect */ + newsprint(drawable); + + if (run_mode != RUN_NONINTERACTIVE) + gimp_displays_flush(); + + /* Store data */ + if (run_mode == RUN_INTERACTIVE) + { + gimp_set_data ("plug_in_newsprint", + &pvals, sizeof (NewsprintValues)); + gimp_set_data ("plug_in_newsprint_ui", + &pvals_ui, sizeof (NewsprintUIValues)); + } + } + else + { + /*gimp_message ("newsprint: cannot operate on indexed images");*/ + status = STATUS_EXECUTION_ERROR; + } + } + + values[0].data.d_status = status; + + gimp_drawable_detach(drawable); +} + + + + +/* create new menu state, and the preview widgets for it */ +static channel_st * +new_preview(gint *sfn) +{ + channel_st *st; + GtkWidget *preview; + GtkWidget *label; + gint i; + + st = g_new(channel_st, 1); + + st->spotfn_num = sfn; + + /* make the preview widgets */ + for(i=0; i < 3; i++) + { + preview = gtk_preview_new(GTK_PREVIEW_COLOR); + gtk_preview_size(GTK_PREVIEW(preview), + SPOT_PREVIEW_SZ*2 + 1, SPOT_PREVIEW_SZ*2 + 1); + gtk_widget_show(preview); + + label = gtk_label_new(""); + gtk_widget_show(label); + + st->prev[i].widget = preview; + st->prev[i].label = label; + /* st->prev[i].value changed by preview_update */ + } + + return st; +} + + +/* the popup menu "st" has changed, so the previews associated with it + * need re-calculation */ +static void +preview_update(channel_st *st) +{ + gint sfn = *(st->spotfn_num); + preview_st *prev; + gint i; + gint x; + gint y; + gint width = SPOT_PREVIEW_SZ * PREVIEW_OVERSAMPLE; + gint oversample = PREVIEW_OVERSAMPLE; + guchar *thresh; + gchar pct[12]; + guchar value; + guchar row[3 * (2 * SPOT_PREVIEW_SZ + 1)]; + + /* If we don't yet have a preview threshold matrix for this spot + * function, generate one now. */ + if (!spotfn_list[sfn].prev_thresh) + { + spotfn_list[sfn].prev_thresh = + spot2thresh(sfn, SPOT_PREVIEW_SZ*PREVIEW_OVERSAMPLE); + } + + + thresh = spotfn_list[sfn].prev_thresh; + + for(i=0; i < 3; i++) + { + prev = &st->prev[i]; + + value = spotfn_list[sfn].prev_lvl[i] * 0xff; + + for (y=0; y <= SPOT_PREVIEW_SZ*2; y++) + { + for (x=0; x <= SPOT_PREVIEW_SZ*2; x++) + { + guint32 sum = 0; + gint sx, sy; + gint tx, ty; + gint rx, ry; + + rx = x * PREVIEW_OVERSAMPLE; + ry = y * PREVIEW_OVERSAMPLE; + + for(sy=-oversample/2; sy<=oversample/2; sy++) + for(sx=-oversample/2; sx<=oversample/2; sx++) + { + tx = rx+sx; + ty = ry+sy; + while(tx < 0) tx += width; + while(ty < 0) ty += width; + while(tx >= width) tx -= width; + while(ty >= width) ty -= width; + + if (value > THRESH(tx, ty)) + sum += 0xff * BARTLETT(sx, sy); + } + sum /= BARTLETT(0,0) * BARTLETT(0,0); + + /* blue lines on cell boundaries */ + if ((x % SPOT_PREVIEW_SZ) == 0 || + (y % SPOT_PREVIEW_SZ) == 0) + { + row[x*3+0] = 0; + row[x*3+1] = 0; + row[x*3+2] = 0xff; + } + else + { + row[x*3+0] = sum; + row[x*3+1] = sum; + row[x*3+2] = sum; + } + } + + gtk_preview_draw_row(GTK_PREVIEW(prev->widget), + row, 0, y, SPOT_PREVIEW_SZ*2+1); + } + + /* redraw preview widget */ + gtk_widget_draw(prev->widget, NULL); + + sprintf(pct, "%2d%%", (int)rint(spotfn_list[sfn].prev_lvl[i] * 100)); + gtk_label_set(GTK_LABEL(prev->label), pct); + } + + gdk_flush (); +} + + +static void +newsprint_menu_callback(GtkWidget *widget, + gpointer data) +{ + channel_st *st = data; + gpointer *ud; + gint menufn; + static gint in_progress = FALSE; + + /* we shouldn't need recursion protection, but if lock_channels is + * set, and gtk_option_menu_set_history ever generates an + * "activated" signmal, then we'll get back here. So we've defensive. */ + if (in_progress) + { + printf("newsprint_menu_callback: unexpected recursion: " + "can't happen\n"); + return; + } + + in_progress = TRUE; + + ud = gtk_object_get_user_data(GTK_OBJECT(widget)); + menufn = (gint)ud; + + *(st->spotfn_num) = menufn; + + preview_update(st); + + /* ripple the change to the other popups if the channels are + * locked together. */ + if (pvals_ui.lock_channels) + { + channel_st *c = st->next; + gint oldfn; + + while(c != st) + { + gtk_option_menu_set_history(GTK_OPTION_MENU(c->option_menu), + menufn); + oldfn = *(c->spotfn_num); + *(c->spotfn_num) = menufn; + if (oldfn != menufn) + preview_update(c); + c = c->next; + } + } + + in_progress = FALSE; +} + + +static void +angle_callback(gdouble new_val, gpointer data) +{ + channel_st *st = data; + channel_st *c; + static gint in_progress = FALSE; + + if (pvals_ui.lock_channels && !in_progress) + { + in_progress = TRUE; + + c = st->next; + + while(c != st) + { + entscale_set_value(c->entscale, new_val); + c = c->next; + } + + in_progress = FALSE; + } +} + + + +static void +newsprint_defaults_callback(GtkWidget *widget, + gpointer data) +{ + NewsprintDialog_st *st = data; + gint saved_lock; + gint cspace; + channel_st **chst; + const chan_tmpl *ct; + gint spotfn; + gint c; + + /* temporarily turn off channel lock */ + saved_lock = pvals_ui.lock_channels; + pvals_ui.lock_channels = FALSE; + + /* for each colourspace, reset its channel info */ + for(cspace=0; cspace < NUM_CS; cspace++) + { + chst = st->chst[cspace]; + ct = cspace_chan_tmpl[cspace]; + + /* skip this colourspace if we haven't used it yet */ + if (!chst[0]) + continue; + + c = 0; + while(ct->name) + { + entscale_set_value(chst[c]->entscale, *(ct->factory_angle)); + + /* change the popup menu and also activate the menuitem in + * question, in order to run the handler that re-computes + * the preview area */ + spotfn = *(ct->factory_spotfn); + gtk_option_menu_set_history(GTK_OPTION_MENU(chst[c]->option_menu), + spotfn); + gtk_menu_item_activate(GTK_MENU_ITEM(chst[c]->menuitem[spotfn])); + + c++; + ct++; + } + } + + pvals_ui.lock_channels = saved_lock; +} + +/* Create (but don't yet show) a new frame for a channel 'widget'. + * Return the channel state, so caller can find the frame and place it + * in a container. */ +static channel_st * +new_channel(const chan_tmpl *ct) +{ + GtkWidget *table; + GtkWidget *label; + GtkWidget *menu; + spot_info_t *sf; + channel_st *chst; + gint i; + + /* create the channel state record */ + chst = new_preview(ct->spotfn); + + chst->frame = gtk_frame_new (ct->name); + gtk_frame_set_shadow_type (GTK_FRAME (chst->frame), GTK_SHADOW_ETCHED_IN); + gtk_container_border_width (GTK_CONTAINER (chst->frame), 4); + + table = gtk_table_new (3, 2, FALSE); + gtk_container_border_width (GTK_CONTAINER (table), 4); + gtk_container_add (GTK_CONTAINER (chst->frame), table); + + /* angle slider */ + chst->entscale = entscale_new(table, 0, 0, "angle", + ENTSCALE_DOUBLE, ct->angle, + -90.0, 90.0, 1.0, TRUE/*constrain*/, + angle_callback, chst); + + /* spot function popup */ + label = gtk_label_new("spot function"); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, + GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show(label); + + chst->option_menu = gtk_option_menu_new(); + gtk_table_attach(GTK_TABLE(table), chst->option_menu, 0, 1, 2, 3, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + gtk_widget_show(chst->option_menu); + + menu = gtk_menu_new(); + + sf = spotfn_list; + i = 0; + while(sf->name) + { + chst->menuitem[i] = gtk_menu_item_new_with_label(sf->name); + gtk_signal_connect(GTK_OBJECT(chst->menuitem[i]), "activate", + (GtkSignalFunc) newsprint_menu_callback, + chst); + gtk_object_set_user_data(GTK_OBJECT(chst->menuitem[i]), (gpointer*)i); + gtk_widget_show(chst->menuitem[i]); + gtk_menu_append(GTK_MENU(menu), GTK_WIDGET(chst->menuitem[i])); + sf++; + i++; + } + + gtk_menu_set_active(GTK_MENU(menu), *ct->spotfn); + + gtk_option_menu_set_menu(GTK_OPTION_MENU(chst->option_menu), menu); + gtk_widget_show(chst->option_menu); + + /* spot function previews go in table slots 1-2, 1-3 (double + * height) */ + { + GtkWidget *sub; + GtkWidget *align; + + sub = gtk_table_new(2, 3, FALSE); + gtk_container_border_width(GTK_CONTAINER(sub), 1); + + align = gtk_alignment_new(0.5, 1.0, 1.0, 0.0); + gtk_container_add(GTK_CONTAINER(align), sub); + gtk_widget_show(align); + + gtk_table_attach(GTK_TABLE(table), align, 1, 2, 1, 3, + GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); + + for(i=0; i < 3; i++) + { + gtk_table_attach(GTK_TABLE(sub), chst->prev[i].widget, + i, i+1, 0, 1, + GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); + + gtk_table_attach(GTK_TABLE(sub), chst->prev[i].label, + i, i+1, 1, 2, + GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); + } + + gtk_widget_show(sub); + } + + /* fire the update once to make sure we start with something + * in the preview windows */ + preview_update(chst); + + gtk_widget_show(table); + + /* deliberately don't show the chst->frame, leave that up to + * the caller */ + + return chst; +} + + +/* Make all the channels needed for "colourspace", and fill in + * the respective channel state fields in "st". */ +static void +gen_channels(NewsprintDialog_st *st, gint colourspace) +{ + const chan_tmpl *ct; + channel_st **chst; + channel_st *base = NULL; + gint i; + + chst = st->chst[colourspace]; + ct = cspace_chan_tmpl[colourspace]; + i = 0; + + while(ct->name) + { + chst[i] = new_channel(ct); + + if (i) + chst[i-1]->next = chst[i]; + else + base = chst[i]; + + gtk_box_pack_start(GTK_BOX(st->vbox), chst[i]->frame, + TRUE, FALSE, 0); + gtk_box_reorder_child(GTK_BOX(st->vbox), chst[i]->frame, 5+i); + + i++; + ct++; + } + + /* make the list circular */ + chst[i-1]->next = base; +} + + +static gint +newsprint_dialog (GDrawable *drawable) +{ + gchar **argv; + gint argc; + /* widgets we need from callbacks stored here */ + NewsprintDialog_st st; + GtkWidget *frame; + GtkWidget *table; + GtkWidget *align; + GtkWidget *button; + GtkWidget *hbox; + GtkWidget *toggle; + GtkWidget *label; + GSList *group = NULL; + gint bpp; + guchar *color_cube; + gint i; + + argc = 1; + argv = g_new(gchar *, 1); + argv[0] = g_strdup("newsprint"); + + gtk_init (&argc, &argv); + gtk_rc_parse(gimp_gtkrc()); + + gtk_preview_set_gamma (gimp_gamma ()); + gtk_preview_set_install_cmap (gimp_install_cmap ()); + color_cube = gimp_color_cube (); + gtk_preview_set_color_cube (color_cube[0], color_cube[1], + color_cube[2], color_cube[3]); + + gtk_widget_set_default_visual (gtk_preview_get_visual ()); + gtk_widget_set_default_colormap (gtk_preview_get_cmap ()); + +#if 0 + printf("newsprint: waiting... (pid %d)\n", getpid()); + kill(getpid(), 19); +#endif + + /* flag values to say we haven't filled these channel + * states in yet */ + for(i=0; iid); + if (gimp_drawable_has_alpha(drawable->id)) + bpp--; + + /* force greyscale if it's the only thing we can do */ + if (bpp == 1) { + pvals.colourspace = CS_GREY; + } else { + if (pvals.colourspace == CS_GREY) + pvals.colourspace = CS_RGB; + } + + st.dlg = gtk_dialog_new (); + gtk_window_set_title (GTK_WINDOW (st.dlg), "Newsprint"); + gtk_window_position (GTK_WINDOW (st.dlg), GTK_WIN_POS_MOUSE); + gtk_signal_connect (GTK_OBJECT (st.dlg), "destroy", + (GtkSignalFunc) newsprint_close_callback, + NULL); + + /* Action area */ + button = gtk_button_new_with_label ("OK"); + GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); + gtk_signal_connect (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) newsprint_ok_callback, + st.dlg); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (st.dlg)->action_area), button, + TRUE, TRUE, 0); + gtk_widget_grab_default (button); + gtk_widget_show (button); + + button = gtk_button_new_with_label ("Cancel"); + GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) gtk_widget_destroy, + GTK_OBJECT (st.dlg)); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (st.dlg)->action_area), button, + TRUE, TRUE, 0); + gtk_widget_show (button); + + /* resolution settings */ + frame = gtk_frame_new ("Resolution"); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN); + gtk_container_border_width (GTK_CONTAINER (frame), 10); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (st.dlg)->vbox), frame, + TRUE, TRUE, 0); + + table = gtk_table_new (3, 2, FALSE); + gtk_container_border_width (GTK_CONTAINER (table), 10); + gtk_container_add (GTK_CONTAINER (frame), table); + + entscale_new(table, 0, 0, "Input SPI ", + ENTSCALE_INT, &pvals_ui.input_spi, + 1.0, 1200.0, 5.0, FALSE/*constrain*/, + NULL, NULL); + + entscale_new(table, 0, 1, "Output LPI ", + ENTSCALE_DOUBLE, &pvals_ui.output_lpi, + 1.0, 1200.0, 5.0, FALSE/*constrain*/, + NULL, NULL); + + entscale_new(table, 0, 2, "Cell size ", + ENTSCALE_INT, &pvals.cell_width, + 3.0, 100.0, 1.0, FALSE/*constrain*/, + NULL, NULL); + /* XXX still need interlocks between these. Maybe use callback fns? */ + + gtk_widget_show(table); + gtk_widget_show(frame); + + + /* screen settings */ + frame = gtk_frame_new ("Screen"); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN); + gtk_container_border_width (GTK_CONTAINER (frame), 10); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (st.dlg)->vbox), frame, + TRUE, TRUE, 0); + + st.vbox = gtk_vbox_new(FALSE, 1); + gtk_container_add (GTK_CONTAINER (frame), st.vbox); + + /* optional portion begins */ + if (bpp != 1) + { + table = gtk_table_new (2, 2, FALSE); + gtk_container_border_width (GTK_CONTAINER (table), 10); + gtk_box_pack_start (GTK_BOX (st.vbox), table, TRUE, TRUE, 0); + + /* black pullout */ + st.pull = entscale_new(table, 0, 1, "Black pullout (%)", + ENTSCALE_INT, &pvals.k_pullout, + 0.0, 100.0, 1.0, TRUE/*constrain*/, + NULL, NULL); + entscale_set_sensitive(st.pull, (pvals.colourspace == CS_CMYK)); + + /* RGB / CMYK / Intensity select */ + label = gtk_label_new("Separate to"); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, + GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show(label); + + hbox = gtk_hbox_new(FALSE, 5); + + toggle = gtk_radio_button_new_with_label(group, "RGB"); + group = gtk_radio_button_group (GTK_RADIO_BUTTON (toggle)); + gtk_box_pack_start (GTK_BOX (hbox), toggle, TRUE, TRUE, 0); + gtk_object_set_user_data(GTK_OBJECT(toggle), &st); + gtk_signal_connect (GTK_OBJECT (toggle), "toggled", + (GtkSignalFunc) newsprint_cspace_update, + GINT_TO_POINTER(CS_RGB)); + gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (toggle), + (pvals.colourspace == CS_RGB)); + gtk_widget_show (toggle); + + toggle = gtk_radio_button_new_with_label (group, "CMYK"); + group = gtk_radio_button_group (GTK_RADIO_BUTTON (toggle)); + gtk_box_pack_start (GTK_BOX (hbox), toggle, TRUE, TRUE, 0); + gtk_object_set_user_data(GTK_OBJECT(toggle), &st); + gtk_signal_connect (GTK_OBJECT (toggle), "toggled", + (GtkSignalFunc) newsprint_cspace_update, + GINT_TO_POINTER(CS_CMYK)); + gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (toggle), + (pvals.colourspace == CS_CMYK)); + gtk_widget_show (toggle); + + toggle = gtk_radio_button_new_with_label (group, "Intensity"); + group = gtk_radio_button_group (GTK_RADIO_BUTTON (toggle)); + gtk_box_pack_start (GTK_BOX (hbox), toggle, TRUE, TRUE, 0); + gtk_object_set_user_data(GTK_OBJECT(toggle), &st); + gtk_signal_connect (GTK_OBJECT (toggle), "toggled", + (GtkSignalFunc) newsprint_cspace_update, + GINT_TO_POINTER(CS_INTENSITY)); + gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (toggle), + (pvals.colourspace == CS_INTENSITY)); + gtk_widget_show (toggle); + + gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 0, 1, + GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show(hbox); + + gtk_widget_show(table); + + /* channel lock & factory defaults button */ + align = gtk_alignment_new(0.0, 1.0, 0.1, 1.0); + hbox = gtk_hbox_new(TRUE, 10); + gtk_container_add(GTK_CONTAINER(align), hbox); + gtk_box_pack_start(GTK_BOX(st.vbox), align, TRUE, FALSE, 0); + /* make sure it went in the right place, since colourspace + * radio button callbacks may have already inserted the + * channel frames */ + gtk_box_reorder_child(GTK_BOX(st.vbox), align, 1); + gtk_widget_show(align); + gtk_widget_show(hbox); + + toggle = gtk_check_button_new_with_label("Lock channels"); + gtk_signal_connect(GTK_OBJECT(toggle), "toggled", + (GtkSignalFunc) newsprint_toggle_update, + &pvals_ui.lock_channels); + gtk_object_set_user_data(GTK_OBJECT(toggle), NULL); + gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON (toggle), + pvals_ui.lock_channels); + gtk_box_pack_start(GTK_BOX(hbox), toggle, TRUE, TRUE, 0); + gtk_widget_show(toggle); + + button = gtk_button_new_with_label("Factory defaults"); + gtk_signal_connect(GTK_OBJECT(button), "clicked", + (GtkSignalFunc) newsprint_defaults_callback, + &st); + gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0); + gtk_widget_show(button); + } + + /* Make the channels appropriate for this colourspace and + * currently selected defaults. They may have already been + * created as a result of callbacks to cspace_update from + * gtk_toggle_button_set_state(). Other channel frames are + * created lazily the first time they are required. */ + if (!st.chst[pvals.colourspace][0]) + { + channel_st **chst; + + gen_channels(&st, pvals.colourspace); + + chst = st.chst[pvals.colourspace]; + + for(i=0; i < cspace_nchans[pvals.colourspace]; i++) + gtk_widget_show(GTK_WIDGET(chst[i]->frame)); + } + + gtk_widget_show(st.vbox); + gtk_widget_show(frame); + + + + /* anti-alias control */ + frame = gtk_frame_new ("Anti-alias"); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN); + gtk_container_border_width (GTK_CONTAINER (frame), 10); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (st.dlg)->vbox), frame, + TRUE, TRUE, 0); + + table = gtk_table_new (1, 2, FALSE); + gtk_container_border_width (GTK_CONTAINER (table), 10); + gtk_container_add (GTK_CONTAINER (frame), table); + + entscale_new(table, 1, 0, "oversample ", + ENTSCALE_INT, &pvals.oversample, + 1.0, 15.0, 1.0, TRUE/*constrain*/, + NULL, NULL); + + gtk_widget_show (table); + gtk_widget_show (frame); + + gtk_widget_show (st.dlg); + + gtk_main (); + gdk_flush (); + + return pint.run; +} + +/* Newsprint interface functions */ + +static void +newsprint_close_callback (GtkWidget *widget, + gpointer data) +{ + gtk_main_quit (); +} + +static void +newsprint_ok_callback (GtkWidget *widget, + gpointer data) +{ + pint.run = TRUE; + gtk_widget_destroy (GTK_WIDGET (data)); +} + +static void +newsprint_toggle_update (GtkWidget *widget, + gpointer data) +{ + int *toggle_val; + + toggle_val = (int *) data; + + *toggle_val = GTK_TOGGLE_BUTTON(widget)->active; +} + + +static void +newsprint_cspace_update (GtkWidget *widget, + gpointer data) +{ + NewsprintDialog_st *st; + gint new_cs = GPOINTER_TO_INT(data); + gint old_cs = pvals.colourspace; + channel_st **chst; + gint i; + + st = gtk_object_get_user_data(GTK_OBJECT(widget)); + + if (!st) + printf("newsprint: cspace_update: no state, can't happen!\n"); + + if (st) + { + /* the CMYK widget looks after the black pullout widget */ + if (new_cs == CS_CMYK) + { + entscale_set_sensitive(st->pull, + GTK_TOGGLE_BUTTON(widget)->active); + } + + /* if we're not activate, then there's nothing more to do */ + if (!GTK_TOGGLE_BUTTON(widget)->active) + return; + + pvals.colourspace = new_cs; + + /* make sure we have the necessary channels for the new + * colourspace */ + if (!st->chst[new_cs][0]) + gen_channels(st, new_cs); + + /* hide the old channels (if any) */ + if (st->chst[old_cs][0]) + { + chst = st->chst[old_cs]; + + for(i=0; i < cspace_nchans[old_cs]; i++) + gtk_widget_hide(GTK_WIDGET(chst[i]->frame)); + } + + /* show the new channels */ + chst = st->chst[new_cs]; + for(i=0; i < cspace_nchans[new_cs]; i++) + gtk_widget_show(GTK_WIDGET(chst[i]->frame)); + } +} + + + + +/* + * Newsprint Effect + */ + + +/*************************************************************/ +/* Spot functions */ + + +/* Spot functions define the order in which pixels should be whitened + * as a cell lightened in colour. They are defined over the entire + * cell, and are called over each pixel in the cell. The cell + * co-ordinate space ranges from -1.0 .. +1.0 inclusive, in both x- and + * y-axes. + * + * This means the spot function f(x, y) must be defined for: + * -1 <= x <= +1, where x is a real number, and + * -1 <= y <= +1, where y is a real number. + * + * The function f's range is -1.0 .. +1.0 inclusive, but it is + * permissible for f to return values outside this range: the nearest + * valid value will be used instead. NOTE: this is in contrast with + * PostScript spot functions, where it is a RangeError for the spot + * function to go outside these limits. + * + * An initially black cell is filled from lowest spot function value + * to highest. The actual values returned do not matter - it is their + * relative orderings that count. This means that spot functions do + * not need to be tonally balanced. A tonally balanced spot function + * is one which for all slices though the function (eg say at z), the + * area of the slice = 4z. In particular, almost all PostScript spot + * functions are _not_ tonally balanced. + */ + + +/* The classic growing dot spot function. This one isn't tonally + * balanced. It can be made so, but it's _really_ ugly. Thanks to + * Richard Mortier for this one: + * + * #define a(r) \ + * ((r<=1)? M_PI * (r*r) : \ + * 4 * sqrt(r*r - 1) + M_PI*r*r - 4*r*r*acos(1/r)) + * + * radius = sqrt(x*x + y*y); + * + * return a(radius) / 4; */ +static gdouble +spot_round(gdouble x, gdouble y) +{ + return 1 - (x*x + y*y); +} + + +/* Another commonly seen spot function is the v-shaped wedge. Tonally + * balanced. */ +static gdouble +spot_line(gdouble x, gdouble y) +{ + return ABS(y); +} + + +/* Square/Diamond dot that never becomes round. Tonally balanced. */ +static gdouble +spot_diamond(gdouble x, gdouble y) +{ + gdouble xy = ABS(x) + ABS(y); + /* spot only valid for 0 <= xy <= 2 */ + return ((xy <= 1)? 2*xy*xy : 2*xy*xy - 4*(xy-1)*(xy-1)) / 4; +} + + + +/* The following functions were derived from a peice of PostScript by + * Peter Fink and published in his book, "PostScript Screening: Adobe + * Accurate Screens" (Adobe Press, 1992). Adobe Systems Incorporated + * allow its use, provided the following copyright message is present: + * + * % Film Test Pages for Screenset Development + * % Copyright (c) 1991 and 1992 Adobe Systems Incorporated + * % All rights reserved. + * % + * % NOTICE: This code is copyrighted by Adobe Systems Incorporated, and + * % may not be reproduced for sale except by permission of Adobe Systems + * % Incorporated. Adobe Systems Incorporated grants permission to use + * % this code for the development of screen sets for use with Adobe + * % Accurate Screens software, as long as the copyright notice remains + * % intact. + * % + * % By Peter Fink 1991/1992 + */ + +/* Square (or Euclidean) dot. Also very common. */ +static gdouble +spot_PSsquare(gdouble x, gdouble y) +{ + gdouble ax = ABS(x); + gdouble ay = ABS(y); + + return (ax+ay)>1? ((ay-1)*(ay-1) + (ax-1)*(ax-1)) - 1 : 1-(ay*ay + ax*ax); +} + +/* Diamond spot function, again from Peter Fink's PostScript + * original. Copyright as for previous function. */ +static gdouble +spot_PSdiamond(gdouble x, gdouble y) +{ + gdouble ax = ABS(x); + gdouble ay = ABS(y); + + return (ax+ay)<=0.75? 1-(ax*ax + ay*ay) : /* dot */ + ( (ax+ay)<=1.23? 1-((ay*0.76) + ax) : /* to diamond (0.76 distort) */ + ((ay-1)*(ay-1) + (ax-1)*(ax-1)) -1); /* back to dot */ +} + +/* end of Adobe Systems Incorporated copyrighted functions */ + + + + + +/*************************************************************/ +/* Spot function to threshold matrix conversion */ + + +/* Each call of the spot function results in one of these */ +typedef struct { + gint index; /* (y * width) + x */ + gdouble value; /* return value of the spot function */ +} order_t; + +/* qsort(3) compare function */ +static int +order_cmp(const void *va, const void *vb) +{ + const order_t *a = va; + const order_t *b = vb; + + return (a->value < b->value)? -1 : ((a->value > b->value)? +1 : 0); +} + +/* Convert spot function "type" to a threshold matrix of size "width" + * times "width". Returns newly allocated threshold matrix. The + * reason for qsort()ing the results rather than just using the spot + * function's value directly as the threshold value is that we want to + * ensure that the threshold matrix is tonally balanced - that is, for + * a threshold value of x%, x% of the values in the matrix are < x%. + * + * Actually, it turns out that qsort()ing a function which is already + * balanced can quite significantly detract from the quality of the + * final result. This is particularly noticable with the line or + * diamond spot functions at 45 degrees. This is because if the spot + * function has multiple locations with the same value, qsort may use + * them in any order. Often, there is quite clearly an optimal order + * however. By marking functions as pre-balanced, this random + * shuffling is avoided. WARNING: a non-balanced spot function marked + * as pre-balanced is bad: you'll end up with dark areas becoming too + * dark or too light, and vice versa for light areas. This is most + * easily checked by halftoning an area, then bluring it back - you + * should get the same colour back again. The only way of getting a + * correctly balanced function is by getting a formula for the spot's + * area as a function of x and y - this can be fairly tough (ie + * possiblly an integral in two dimensions that must be solved + * analytically). + * + * The threshold matrix is used to compare against image values. If + * the image value is greater than the threshold value, then the + * output pixel is illuminated. This means that a threshold matrix + * entry of 0 never causes output pixels to be illuminated. */ +static guchar * +spot2thresh(gint type, gint width) +{ + gdouble sx, sy; + gdouble val; + spotfn_t *spotfn; + guchar *thresh; + order_t *order; + gint x, y; + gint i; + gint wid2 = width*width; + gint balanced = spotfn_list[type].balanced; + + thresh = g_new(guchar, wid2); + spotfn = spotfn_list[type].fn; + + order = g_new(order_t, wid2); + + i = 0; + for(y=0; yid, &x1, &y1, &x2, &y2); + + bpp = gimp_drawable_bpp(drawable->id); + has_alpha = gimp_drawable_has_alpha(drawable->id); + colour_bpp = has_alpha? bpp-1 : bpp; + colourspace= pvals.colourspace; + if (bpp == 1) { + colourspace = CS_GREY; + } else { + if (colourspace == CS_GREY) + colourspace = CS_RGB; + } + + /* Bartlett window matrix optimisation */ + w002 = BARTLETT(0,0) * BARTLETT(0,0); +#if 0 + /* It turns out to be slightly slower to cache a pre-computed + * bartlett matrix! I put it down to d-cache pollution *shrug* */ + wgt = g_new(gint, oversample*oversample); + for(y=-oversample/2; y<=oversample/2; y++) + for(x=-oversample/2; x<=oversample/2; x++) + WGT(x,y) = BARTLETT(x,y); +#endif /* 0 */ + +#define ASRT(_x) \ +do { \ + if (!VALID_SPOTFN(_x)) \ + { \ + printf("newsprint: %d is not a valid spot type\n", _x); \ + _x = SPOTFN_DOT; \ + } \ +} while(0) + + /* calculate the RGB / CMYK rotations and threshold matrices */ + if (bpp == 1 || colourspace == CS_INTENSITY) + { + rot[0] = DEG2RAD(pvals.gry_ang); + thresh[0] = spot2thresh(pvals.gry_spotfn, width); + } + else + { + gint rf = pvals.red_spotfn; + gint gf = pvals.grn_spotfn; + gint bf = pvals.blu_spotfn; + + rot[0] = DEG2RAD(pvals.red_ang); + rot[1] = DEG2RAD(pvals.grn_ang); + rot[2] = DEG2RAD(pvals.blu_ang); + + /* always need at least one threshold matrix */ + ASRT(rf); + spotfn_list[rf].thresh = spot2thresh(rf, width); + thresh[0] = spotfn_list[rf].thresh; + + /* optimisation: only use separate threshold matrices if the + * spot functions are actually different */ + ASRT(gf); + if (!spotfn_list[gf].thresh) + spotfn_list[gf].thresh = spot2thresh(gf, width); + thresh[1] = spotfn_list[gf].thresh; + + ASRT(bf); + if (!spotfn_list[bf].thresh) + spotfn_list[bf].thresh = spot2thresh(bf, width); + thresh[2] = spotfn_list[bf].thresh; + + if (colourspace == CS_CMYK) + { + rot[3] = DEG2RAD(pvals.gry_ang); + gf = pvals.gry_spotfn; + ASRT(gf); + if (!spotfn_list[gf].thresh) + spotfn_list[gf].thresh = spot2thresh(gf, width); + thresh[3] = spotfn_list[gf].thresh; + } + } + + + + /* Initialize progress */ + progress = 0; + max_progress = (x2 - x1) * (y2 - y1); + + TM_START(); + + for( y = y1; y < y2; y += tile_width - ( y % tile_width ) ) + { + for( x = x1; x < x2; x += tile_width - ( x % tile_width ) ) + { + /* snap to tile boundary */ + x_step = tile_width - ( x % tile_width ); + y_step = tile_width - ( y % tile_width ); + /* don't step off the end of the image */ + x_step = MIN( x_step, x2-x ); + y_step = MIN( y_step, y2-y ); + + /* set up the source and dest regions */ + gimp_pixel_rgn_init (&src_rgn, drawable, x, y, x_step, y_step, + FALSE/*dirty*/, FALSE/*shadow*/); + + gimp_pixel_rgn_init (&dest_rgn, drawable, x, y, x_step, y_step, + TRUE/*dirty*/, TRUE/*shadow*/); + + /* page in the image, one tile at a time */ + for (pr = gimp_pixel_rgns_register (2, &src_rgn, &dest_rgn); + pr != NULL; + pr = gimp_pixel_rgns_process (pr)) + { + src_row = src_rgn.data; + dest_row = dest_rgn.data; + + for (row = 0; row < src_rgn.h; row++) + { + src = src_row; + dest = dest_row; + for (col = 0; col < src_rgn.w; col++) + { + guchar data[4]; + + rx = (x + col) * oversample; + ry = (y + row) * oversample; + + /* convert rx and ry to polar (r, theta) */ + r = sqrt(((double)rx)*rx + ((double)ry)*ry); + theta = atan2(((gdouble)ry), ((gdouble)rx)); + + for(b=0; b= width) tx -= width; + while (ty >= width) ty -= width; + if (data[b] > THRESHn(b, tx, ty)) + sum += 0xff * BARTLETT(sx, sy); + } + sum /= w002; + data[b] = sum; + } + } + if (has_alpha) + dest[colour_bpp] = src[colour_bpp]; + + /* re-pack the colours into RGB */ + switch(colourspace) { + case CS_CMYK: + data[0] = CLAMPED_ADD(data[0], data[3]); + data[1] = CLAMPED_ADD(data[1], data[3]); + data[2] = CLAMPED_ADD(data[2], data[3]); + data[0] = 0xff - data[0]; + data[1] = 0xff - data[1]; + data[2] = 0xff - data[2]; + break; + + case CS_INTENSITY: + if (has_alpha) + { + dest[colour_bpp] = data[0]; + data[0] = 0xff; + } + data[1] = data[1] * data[0] / 0xff; + data[2] = data[2] * data[0] / 0xff; + data[0] = data[3] * data[0] / 0xff; + break; + + default: + /* no other special cases */ + break; + } + + for(b=0; bid, TRUE); + gimp_drawable_update (drawable->id, x1, y1, (x2 - x1), (y2 - y1)); +} + + + + +/**************************************************************/ +/* Support routines */ + +/* Lightly modified entscale: + * o Added entscale_set_sensitive() method */ + + +/*************************************************************************/ +/** **/ +/** +++ Entscale **/ +/** **/ +/*************************************************************************/ + +/* these routines are taken from gflare.c */ + +/* -*- mode:c -*- */ +/* + Entry and Scale pair (int and double integrated) + + This is an attempt to combine entscale_int and double. + Never compiled yet. + + TODO: + - Do the proper thing when the user changes value in entry, + so that callback should not be called when value is actually not changed. + - Update delay + */ + + +static void entscale_destroy_callback (GtkWidget *widget, + gpointer data); +static void entscale_scale_update (GtkAdjustment *adjustment, + gpointer data); +static void entscale_entry_update (GtkWidget *widget, + gpointer data); +/* + * Create an entry, a scale and a label, then attach them to + * specified table. + * 1 row and 2 cols of table are needed. + * + * Input: + * table: table which entscale is attached to + * x, y: starting row and col in table + * caption: label string + * type: type of variable (ENTSCALE_INT or ENTSCALE_DOUBLE) + * variable: pointer to variable + * min, max: boundary of scale + * step: step of scale (ignored when (type == ENTSCALE_INT)) + * constraint: (bool) true iff the value of *variable should be + * constraint by min and max + * callback: called when the value is actually changed + * (*variable is automatically changed, so there's no + * need of callback func usually.) + * call_data: data for callback func + */ + +Entscale * +entscale_new (GtkWidget *table, gint x, gint y, gchar *caption, + EntscaleType type, gpointer variable, + gdouble min, gdouble max, gdouble step, + gint constraint, + EntscaleCallbackFunc callback, + gpointer call_data) +{ + Entscale *entscale; + GtkWidget *entry; + GtkWidget *scale; + GtkObject *adjustment; + gchar buffer[256]; + gdouble val; + gdouble constraint_val; + + entscale = g_new0 (Entscale, 1 ); + entscale->type = type; + entscale->constraint = constraint; + entscale->callback = callback; + entscale->call_data = call_data; + + switch (type) + { + case ENTSCALE_INT: + step = 1.0; + strcpy (entscale->fmt_string, "%.0f"); + val = *(gint *)variable; + break; + case ENTSCALE_DOUBLE: + sprintf (entscale->fmt_string, "%%.%df", entscale_get_precision (step) + 1); + val = *(gdouble *)variable; + break; + default: + g_error ("TYPE must be either ENTSCALE_INT or ENTSCALE_DOUBLE"); + val = 0; + break; + } + + entscale->label = gtk_label_new (caption); + gtk_misc_set_alignment (GTK_MISC (entscale->label), 0.0, 0.5); + + /* + If the first arg of gtk_adjustment_new() isn't between min and + max, it is automatically corrected by gtk later with + "value_changed" signal. I don't like this, since I want to leave + *variable untouched when `constraint' is false. + The lines below might look oppositely, but this is OK. + */ + if (constraint) + constraint_val = val; + else + constraint_val = BOUNDS (val, min, max); + + adjustment = entscale->adjustment = + gtk_adjustment_new (constraint_val, min, max, step, step, 0.0); + scale = gtk_hscale_new (GTK_ADJUSTMENT (adjustment)); + gtk_widget_set_usize (scale, ENTSCALE_SCALE_WIDTH, 0); + gtk_scale_set_draw_value (GTK_SCALE (scale), FALSE); + + entry = entscale->entry = gtk_entry_new (); + gtk_widget_set_usize (entry, ENTSCALE_ENTRY_WIDTH, 0); + sprintf (buffer, entscale->fmt_string, val); + gtk_entry_set_text (GTK_ENTRY (entry), buffer); + + /* entscale is done */ + gtk_object_set_user_data (GTK_OBJECT(adjustment), entscale); + gtk_object_set_user_data (GTK_OBJECT(entry), entscale); + + /* now ready for signals */ + gtk_signal_connect (GTK_OBJECT (entry), "changed", + (GtkSignalFunc) entscale_entry_update, + variable); + gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed", + (GtkSignalFunc) entscale_scale_update, + variable); + gtk_signal_connect (GTK_OBJECT (entry), "destroy", + (GtkSignalFunc) entscale_destroy_callback, + NULL ); + + /* start packing */ + entscale->hbox = gtk_hbox_new (FALSE, 5); + gtk_box_pack_start (GTK_BOX (entscale->hbox), scale, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (entscale->hbox), entry, FALSE, TRUE, 0); + + gtk_table_attach (GTK_TABLE (table), entscale->label, x, x+1, y, y+1, + GTK_FILL, GTK_FILL, 0, 0); + gtk_table_attach (GTK_TABLE (table), entscale->hbox, x+1, x+2, y, y+1, + GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); + + gtk_widget_show (entscale->label); + gtk_widget_show (entry); + gtk_widget_show (scale); + gtk_widget_show (entscale->hbox); + + return entscale; +} + +static int +entscale_get_precision (gdouble step) +{ + int precision; + if (step <= 0) + return 0; + for (precision = 0; pow (0.1, precision) > step; precision++) ; + return precision; +} + +static void +entscale_destroy_callback (GtkWidget *widget, + gpointer data) +{ + Entscale *entscale; + + entscale = gtk_object_get_user_data (GTK_OBJECT (widget)); + g_free (entscale); +} +static void +entscale_scale_update (GtkAdjustment *adjustment, + gpointer data) +{ + Entscale *entscale; + GtkEntry *entry; + gchar buffer[256]; + gdouble old_val, new_val; + + entscale = gtk_object_get_user_data (GTK_OBJECT (adjustment)); + + new_val = adjustment->value; + /* adjustmet->value is always constrainted */ + + switch (entscale->type) + { + case ENTSCALE_INT: + old_val = *(gint*) data; + *(gint*) data = (gint) new_val; + DEBUG_PRINT (("entscale_scale_update(int): fmt=\"%s\" old=%g new=%g\n", + entscale->fmt_string, old_val, new_val)); + break; + case ENTSCALE_DOUBLE: + old_val = *(gdouble*) data; + *(gdouble*) data = new_val; + DEBUG_PRINT (("entscale_scale_update(double): fmt=\"%s\" old=%g new=%g\n", + entscale->fmt_string, old_val, new_val)); + break; + default: + g_warning ("entscale_scale_update: invalid type"); + return; + } + + entry = GTK_ENTRY (entscale->entry); + sprintf (buffer, entscale->fmt_string, new_val); + + /* avoid infinite loop (scale, entry, scale, entry ...) */ + gtk_signal_handler_block_by_data (GTK_OBJECT(entry), data); + gtk_entry_set_text (entry, buffer); + gtk_signal_handler_unblock_by_data (GTK_OBJECT(entry), data); + + if (entscale->callback && (old_val != new_val)) + (*entscale->callback) (new_val, entscale->call_data); +} + +static void +entscale_entry_update (GtkWidget *widget, + gpointer data) +{ + Entscale *entscale; + GtkAdjustment *adjustment; + gdouble old_val, val, new_val, constraint_val; + + entscale = gtk_object_get_user_data (GTK_OBJECT (widget)); + adjustment = GTK_ADJUSTMENT (entscale->adjustment); + + val = atof (gtk_entry_get_text (GTK_ENTRY (widget))); + + constraint_val = BOUNDS (val, adjustment->lower, adjustment->upper); + + if (entscale->constraint) + new_val = constraint_val; + else + new_val = val; + + switch (entscale->type) + { + case ENTSCALE_INT: + old_val = *(gint*) data; + *(gint*) data = (gint) new_val; + DEBUG_PRINT (("entscale_entry_update(int): old=%g new=%g const=%g\n", + old_val, new_val, constraint_val)); + break; + case ENTSCALE_DOUBLE: + old_val = *(gdouble*) data; + *(gdouble*) data = new_val; + DEBUG_PRINT (("entscale_entry_update(double): old=%g new=%g const=%g\n", + old_val, new_val, constraint_val)); + break; + default: + g_warning ("entscale_entry_update: invalid type"); + return; + } + + adjustment->value = constraint_val; + /* avoid infinite loop (scale, entry, scale, entry ...) */ + gtk_signal_handler_block_by_data (GTK_OBJECT(adjustment), data ); + gtk_signal_emit_by_name (GTK_OBJECT(adjustment), "value_changed"); + gtk_signal_handler_unblock_by_data (GTK_OBJECT(adjustment), data ); + + if (entscale->callback && (old_val != new_val)) + (*entscale->callback) (new_val, entscale->call_data); +} + + +static void entscale_set_sensitive(Entscale *ent, gint sensitive) +{ + gtk_widget_set_sensitive(GTK_WIDGET(ent->label), sensitive); + gtk_widget_set_sensitive(GTK_WIDGET(ent->hbox), sensitive); +} + + +static void entscale_set_value(Entscale *ent, gfloat value) +{ + gtk_adjustment_set_value(GTK_ADJUSTMENT(ent->adjustment), value); +} diff --git a/plug-ins/common/wind.c b/plug-ins/common/wind.c new file mode 100644 index 0000000000..f25371c040 --- /dev/null +++ b/plug-ins/common/wind.c @@ -0,0 +1,1105 @@ +/* + * wind - a plug-in for the GIMP + * + * Copyright (C) Nigel Wetten + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Contact info: nigel@cs.nwu.edu + * + * Version: 1.0.0 + */ + +#include +#include + +#include +#include "libgimp/gimp.h" + + +#define PLUG_IN_NAME "wind" + +#define COMPARE_WIDTH 3 + +#define ENTRY_WIDTH 40 +#define SCALE_WIDTH 200 +#define MIN_THRESHOLD 0 +#define MAX_THRESHOLD 50 +#define MIN_STRENGTH 1 +#define MAX_STRENGTH 50 + +#define NEGATIVE_STRENGTH_TEXT "\n Wind Strength must be greater than 0. \n" +#define THRESHOLD_TEXT "Higher values restrict the effect to fewer areas of the image" +#define STRENGTH_TEXT "Higher values increase the magnitude of the effect" +#define WIND_TEXT "A fine grained algorithm" +#define BLAST_TEXT "A coarse grained algorithm" +#define LEFT_TEXT "Makes the wind come from the left" +#define RIGHT_TEXT "Makes the wind come from the right" +#define LEADING_TEXT "The effect is applied at the leading edge of objects" +#define TRAILING_TEXT "The effect is applied at the trailing edge of objects" +#define BOTH_TEXT "The effect is applied at both edges of objects" + +typedef enum {LEFT, RIGHT} direction_t; +typedef enum {RENDER_WIND, RENDER_BLAST} algorithm_t; +typedef enum {BOTH, LEADING, TRAILING} edge_t; + + +static void query(void); +static void run(char *name, int nparams, GParam *param, + int *nreturn_vals, GParam **return_vals); +static void dialog_box(void); +static gint render_effect(GDrawable *drawable); +static void render_wind(GDrawable *drawable, gint threshold, gint strength, + direction_t direction, edge_t edge); +static void render_blast(GDrawable *drawable, gint threshold, gint strength, + direction_t direction, edge_t edge); +static gint render_blast_row(guchar *buffer, gint bytes, gint lpi, gint threshold, + gint strength, edge_t edge); +static void render_wind_row(guchar *sb, gint bytes, gint lpi, gint threshold, + gint strength, edge_t edge); +static void msg_ok_callback(GtkWidget *widget, gpointer data); +static void msg_close_callback(GtkWidget *widget, gpointer data); +static void close_callback(GtkWidget *widget, gpointer data); +static void ok_callback(GtkWidget *widget, gpointer data); +static void entry_callback(GtkWidget *widget, gpointer data); +static void radio_button_alg_callback(GtkWidget *widget, gpointer data); +static void radio_button_direction_callback(GtkWidget *widget, gpointer data); +static void get_derivative(guchar *pixel_R1, guchar *pixel_R2, + edge_t edge, gint *derivative_R, + gint *derivative_G, gint *derivative_B); +static gint threshold_exceeded(guchar *pixel_R1, guchar *pixel_R2, + edge_t edge, gint threshold); +static void reverse_buffer(guchar *buffer, gint length, gint bytes); +static void modal_message_box(gchar *text); + +GPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run /* run_proc */ +}; + + +/********************* + Globals + *******************/ + +/* This is needed to communicate the result from the dialog + box button's callback function*/ +gint dialog_result = -1; + +struct config_tag +{ + gint threshold; /* derivative comparison for edge detection */ + direction_t direction; /* of wind, LEFT or RIGHT */ + gint strength; /* how many pixels to bleed */ + algorithm_t alg; /* which algorithm */ + edge_t edge; /* controls abs, negation of derivative */ +}; + +typedef struct config_tag config_t; +config_t config = +{ + 10, /* threshold for derivative edge detection */ + LEFT, /* bleed to the right */ + 10, /* how many pixels to bleed */ + RENDER_WIND, /* default algorithm */ + LEADING /* abs(derivative); */ +}; + + + +MAIN() + +static void +query(void) +{ + static GParamDef args[] = + { + {PARAM_INT32, "run_mode", "Interactive, non-interactive"}, + {PARAM_IMAGE, "image", "Input image (unused)"}, + {PARAM_DRAWABLE, "drawable", "Input drawable"}, + {PARAM_INT32, "threshold", "Controls where blending will be done >= 0"}, + {PARAM_INT32, "direction", "Left or Right: 0 or 1"}, + {PARAM_INT32, "strength", "Controls the extent of the blending > 1"}, + {PARAM_INT32, "alg", "WIND, BLAST"}, + {PARAM_INT32, "edge", "LEADING, TRAILING, or BOTH"} + }; + static GParamDef *return_vals = NULL; + static int nargs = sizeof(args) / sizeof(args[0]); + static int nreturn_vals = 0; + + gimp_install_procedure("plug_in_wind", + "Renders a wind effect.", + "Renders a wind effect.", + "Nigel Wetten", + "Nigel Wetten", + "1998", + "/Filters/Distorts/Wind", + "RGB*", + PROC_PLUG_IN, + nargs, nreturn_vals, + args, return_vals); + return; +} /* query */ + +/*****/ + +static void +run(gchar *name, gint nparams, GParam *param, gint *nreturn_vals, + GParam **return_vals) +{ + static GParam values[1]; + GDrawable *drawable; + GRunModeType run_mode; + GStatusType status = STATUS_SUCCESS; + + run_mode = param[0].data.d_int32; + drawable = gimp_drawable_get(param[2].data.d_drawable); + gimp_tile_cache_ntiles(drawable->width / gimp_tile_width() + 1); + switch (run_mode) + { + case RUN_NONINTERACTIVE: + if (nparams == 8) + { + config.threshold = param[3].data.d_int32; + config.direction = param[4].data.d_int32; + config.strength = param[5].data.d_int32; + config.alg = param[6].data.d_int32; + config.edge = param[7].data.d_int32; + if (render_effect(drawable) == -1) + { + status = STATUS_EXECUTION_ERROR; + } + } + else + { + status = STATUS_CALLING_ERROR; + } + break; + + case RUN_INTERACTIVE: + gimp_get_data("plug_in_wind", &config); + dialog_box(); + if (dialog_result == -1) + { + status = STATUS_EXECUTION_ERROR; + break; + } + if (render_effect(drawable) == -1) + { + status = STATUS_CALLING_ERROR; + break; + } + gimp_set_data("plug_in_wind", &config, sizeof(config_t)); + gimp_displays_flush(); + break; + + case RUN_WITH_LAST_VALS: + gimp_get_data("plug_in_wind", &config); + if (render_effect(drawable) == -1) + { + status = STATUS_EXECUTION_ERROR; + gimp_message("An execution error occured."); + } + else + { + gimp_displays_flush(); + } + + } /* switch */ + + gimp_drawable_detach(drawable); + + *nreturn_vals = 1; + *return_vals = values; + values[0].type = PARAM_STATUS; + values[0].data.d_status = status; + + return; +} /* run */ + +/*****/ + +static gint +render_effect(GDrawable *drawable) +{ + if (config.alg == RENDER_WIND) + { + gimp_progress_init("Rendering Wind..."); + render_wind(drawable, config.threshold, config. strength, + config.direction, config.edge); + } + else if (config.alg == RENDER_BLAST) + { + gimp_progress_init("Rendering Blast..."); + render_blast(drawable, config.threshold, config.strength, + config.direction, config.edge); + } + return 0; +} /* render_effect */ + +/*****/ + +static void +render_blast(GDrawable *drawable, gint threshold, gint strength, + direction_t direction, edge_t edge) +{ + gint x1, x2, y1, y2; + gint width = drawable->width; + gint height = drawable->height; + gint bytes = drawable->bpp; + guchar *buffer; + GPixelRgn src_region, dest_region; + gint row; + gint row_stride = width * bytes; + gint marker = 0; + gint lpi = row_stride - bytes; + + gimp_drawable_mask_bounds(drawable->id, &x1, &y1, &x2, &y2); + gimp_pixel_rgn_init(&src_region, drawable, 0, 0, width, height, FALSE, FALSE); + gimp_pixel_rgn_init(&dest_region, drawable, 0, 0, width, height, TRUE, TRUE); + + buffer = malloc(row_stride); + + for (row = y1; row < y2; row++) + { + + gimp_pixel_rgn_get_row(&src_region, buffer, x1, row, width); + if (direction == RIGHT) + { + reverse_buffer(buffer, row_stride, bytes); + } + marker = render_blast_row(buffer, bytes, lpi, threshold, strength, edge); + if (direction == RIGHT) + { + reverse_buffer(buffer, row_stride, bytes); + } + gimp_pixel_rgn_set_row(&dest_region, buffer, x1, row, width); + gimp_progress_update((double) row / (double) (y2 - y1)); + if (marker) + { + gint j, limit; + + limit = 1 + rand() % 2; + for (j = 0; (j < limit) && (row < y2); j++) + { + row++; + if (row < y2) + { + gimp_pixel_rgn_get_row(&src_region, buffer, x1, row, width); + gimp_pixel_rgn_set_row(&dest_region, buffer, x1, row, width); + } + } + marker = 0; + } + } /* for */ + free(buffer); + + /* update the region */ + gimp_drawable_flush(drawable); + gimp_drawable_merge_shadow(drawable->id, TRUE); + gimp_drawable_update(drawable->id, x1, y1, x2 - x1, y2 - y1); + + return; +} /* render_blast */ + +/*****/ + +static void +render_wind(GDrawable *drawable, gint threshold, gint strength, + direction_t direction, edge_t edge) +{ + GPixelRgn src_region, dest_region; + gint width = drawable->width; + gint height = drawable->height; + gint bytes = drawable->bpp; + gint row_stride = width * bytes; + gint comp_stride = bytes * COMPARE_WIDTH; + gint row; + guchar *sb; + gint lpi = row_stride - comp_stride; + gint x1, y1, x2, y2; + + gimp_drawable_mask_bounds(drawable->id, &x1, &y1, &x2, &y2); + gimp_pixel_rgn_init(&src_region, drawable, 0, 0, width, height, + FALSE, FALSE); + gimp_pixel_rgn_init(&dest_region, drawable, 0, 0, width, height, TRUE, TRUE); + sb = g_malloc(row_stride); + + for (row = y1; row < y2; row++) + { + gimp_pixel_rgn_get_row(&src_region, sb, x1, row, width); + if (direction == RIGHT) + { + reverse_buffer(sb, row_stride, bytes); + } + render_wind_row(sb, bytes, lpi, threshold, strength, edge); + if (direction == RIGHT) + { + reverse_buffer(sb, row_stride, bytes); + } + gimp_pixel_rgn_set_row(&dest_region, sb, x1, row, width); + gimp_progress_update((gdouble) row / (gdouble) (y2 - y1)); + } + free(sb); + gimp_drawable_flush(drawable); + gimp_drawable_merge_shadow(drawable->id, TRUE); + gimp_drawable_update(drawable->id, x1, y1, x2 - x1, y2 - y1); + return; +} /* render_wind */ + +/*****/ + +static gint +render_blast_row(guchar *buffer, gint bytes, gint lpi, gint threshold, + gint strength, edge_t edge) +{ + gint Ri, Gi, Bi; + gint sbi, lbi; + gint bleed_length; + gint i, j; + gint weight, random_factor; + gint skip = 0; + + for (j = 0; j < lpi; j += bytes) + { + Ri = j; Gi = j + 1; Bi = j + 2; + + if (threshold_exceeded(buffer+Ri, buffer+Ri+bytes, edge, threshold)) + { + /* we have found an edge, do bleeding */ + sbi = Ri; + + weight = rand() % 10; + if (weight > 5) + { + random_factor = 2; + } + else if (weight > 3) + { + random_factor = 3; + } + else + { + random_factor = 4; + } + bleed_length = 0; + switch (rand() % random_factor) + { + case 3: + bleed_length += strength; + /* fall through to add up multiples of strength */ + case 2: + bleed_length += strength; + /* fall through */ + case 1: + bleed_length += strength; + /* fall through */ + case 0: + bleed_length += strength; + /* fall through */ + } + + lbi = sbi + bytes * bleed_length; + if (lbi > lpi) + { + lbi = lpi; + } + + for (i = sbi; i < lbi; i += bytes) + { + buffer[i] = buffer[Ri]; + buffer[i+1] = buffer[Gi]; + buffer[i+2] = buffer[Bi]; + } + j = lbi - bytes; + if ((rand() % 10) > 7) + { + skip = 1; + } + } /* if */ + } /* for j=0 */ + return skip; +} /* render_blast_row */ + +/*****/ + +static void +render_wind_row(guchar *sb, gint bytes, gint lpi, gint threshold, + gint strength, edge_t edge) +{ + gint i, j; + gint bleed_length; + gint blend_amt_R, blend_amt_G, blend_amt_B; + gint blend_colour_R, blend_colour_G, blend_colour_B; + gint target_colour_R, target_colour_G, target_colour_B; + gdouble bleed_length_max; + gint bleed_variation; + gint n; + gint sbi; /* starting bleed index */ + gint lbi; /* last bleed index */ + gdouble denominator; + gint comp_stride = bytes * COMPARE_WIDTH; + + for (j = 0; j < lpi; j += bytes) + { + gint Ri = j; + gint Gi = j + 1; + gint Bi = j + 2; + + if (threshold_exceeded(sb+Ri, sb+Ri+comp_stride, edge, threshold)) + { + /* we have found an edge, do bleeding */ + sbi = Ri + comp_stride; + blend_colour_R = sb[Ri]; + blend_colour_G = sb[Gi]; + blend_colour_B = sb[Bi]; + target_colour_R = sb[sbi]; + target_colour_G = sb[sbi+1]; + target_colour_B = sb[sbi+2]; + bleed_length_max = strength; + + if (rand() % 3) /* introduce weighted randomness */ + { + bleed_length_max = strength; + } + else + { + bleed_length_max = 4 * strength; + } + + bleed_variation = 1 + + (gint) (bleed_length_max * rand() / (RAND_MAX + 1.0)); + + lbi = sbi + bleed_variation * bytes; + if (lbi > lpi) + { + lbi = lpi; /* stop overunning the buffer */ + } + + bleed_length = bleed_variation; + + blend_amt_R = target_colour_R - blend_colour_R; + blend_amt_G = target_colour_G - blend_colour_G; + blend_amt_B = target_colour_B - blend_colour_B; + denominator = bleed_length * bleed_length + bleed_length; + denominator = 2.0 / denominator; + n = bleed_length; + for (i = sbi; i < lbi; i += bytes) + { + + /* check against original colour */ + if (!threshold_exceeded(sb+Ri, sb+i, edge, threshold) + && (rand() % 2)) + { + break; + } + + blend_colour_R += blend_amt_R * n * denominator; + blend_colour_G += blend_amt_G * n * denominator; + blend_colour_B += blend_amt_B * n * denominator; + + if (blend_colour_R > 255) blend_colour_R = 255; + else if (blend_colour_R < 0) blend_colour_R = 0; + if (blend_colour_G > 255) blend_colour_G = 255; + else if (blend_colour_G < 0) blend_colour_G = 0; + if (blend_colour_B > 255) blend_colour_B = 255; + else if (blend_colour_B < 0) blend_colour_B = 0; + + sb[i] = (blend_colour_R * 2 + sb[i]) / 3; + sb[i+1] = (blend_colour_G * 2 + sb[i+1]) / 3; + sb[i+2] = (blend_colour_B * 2 + sb[i+2]) / 3; + + if (threshold_exceeded(sb+i, sb+i+comp_stride, BOTH, + threshold)) + { + target_colour_R = sb[i+comp_stride]; + target_colour_G = sb[i+comp_stride+1]; + target_colour_B = sb[i+comp_stride+2]; + blend_amt_R = target_colour_R - blend_colour_R; + blend_amt_G = target_colour_G - blend_colour_G; + blend_amt_B = target_colour_B - blend_colour_B; + denominator = n * n + n; + denominator = 2.0 / denominator; + } + n--; + } + } /* if */ + } /* for j=0 */ + return; +} /* render_wind_row */ + +/*****/ + +static gint +threshold_exceeded(guchar *pixel_R1, guchar *pixel_R2, edge_t edge, + gint threshold) +{ + gint derivative_R, derivative_G, derivative_B; + gint return_value; + + get_derivative(pixel_R1, pixel_R2, edge, + &derivative_R, &derivative_G, &derivative_B); + + if(((derivative_R + derivative_G + derivative_B) / 3) > threshold) + { + return_value = 1; + } + else + { + return_value = 0; + } + return return_value; +} /* threshold_exceeded */ + +/*****/ + +static void +get_derivative(guchar *pixel_R1, guchar *pixel_R2, edge_t edge, + gint *derivative_R, gint *derivative_G, gint *derivative_B) +{ + guchar *pixel_G1 = pixel_R1 + 1; + guchar *pixel_B1 = pixel_R1 + 2; + guchar *pixel_G2 = pixel_R2 + 1; + guchar *pixel_B2 = pixel_R2 + 2; + + *derivative_R = *pixel_R2 - *pixel_R1; + *derivative_G = *pixel_G2 - *pixel_G1; + *derivative_B = *pixel_B2 - *pixel_B1; + + if (edge == BOTH) + { + *derivative_R = abs(*derivative_R); + *derivative_G = abs(*derivative_G); + *derivative_B = abs(*derivative_B); + } + else if (edge == LEADING) + { + *derivative_R = -(*derivative_R); + *derivative_G = -(*derivative_G); + *derivative_B = -(*derivative_B); + } + else if (edge == TRAILING) + { + /* no change needed */ + } + return; +} /* get_derivative */ + +/*****/ + +static void +reverse_buffer(guchar *buffer, gint length, gint bytes) +{ + gint i, si; + gint temp; + gint midpoint; + + midpoint = length / 2; + for (i = 0; i < midpoint; i += bytes) + { + si = length - bytes - i; + + temp = buffer[i]; + buffer[i] = buffer[si]; + buffer[si] = (guchar) temp; + + temp = buffer[i+1]; + buffer[i+1] = buffer[si+1]; + buffer[si+1] = (guchar) temp; + + temp = buffer[i+2]; + buffer[i+2] = buffer[si+2]; + buffer[si+2] = (guchar) temp; + } + + return; +} /* reverse_buffer */ + +/*****/ + +/*************************************************** + GUI + ***************************************************/ + +static void +msg_ok_callback(GtkWidget *widget, gpointer data) +{ + gtk_grab_remove(GTK_WIDGET(data)); + gtk_widget_destroy(GTK_WIDGET(data)); + return; +} /* msg_ok_callback */ + +/*****/ + +static void +msg_close_callback(GtkWidget *widget, gpointer data) +{ + return; +} /* msg_close_callback */ + +/*****/ + +static void +modal_message_box(gchar *text) +{ + GtkWidget *message_box; + GtkWidget *button; + GtkWidget *label; + + message_box = gtk_dialog_new(); + gtk_window_set_title(GTK_WINDOW(message_box), "Ooops!"); + gtk_window_position(GTK_WINDOW(message_box), GTK_WIN_POS_MOUSE); + gtk_signal_connect(GTK_OBJECT(message_box), "destroy", + (GtkSignalFunc) msg_close_callback, NULL); + + label = gtk_label_new(text); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(message_box)->vbox), label, + TRUE, TRUE, 0); + gtk_widget_show(label); + + button = gtk_button_new_with_label("OK"); + GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); + gtk_signal_connect(GTK_OBJECT(button), "clicked", + (GtkSignalFunc) msg_ok_callback, message_box); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(message_box)->action_area), + button, TRUE, TRUE, 0); + gtk_widget_grab_default(button); + gtk_widget_show(button); + + gtk_grab_add(message_box); + gtk_widget_show(message_box); + return; +} /* modal_message_box */ + +/*****/ + +static void +close_callback(GtkWidget *widget, gpointer data) +{ + gtk_main_quit(); + return; +} /* close_callback */ + +/*****/ + +static void +ok_callback(GtkWidget *widget, gpointer data) +{ + /* we have to stop the dialog from being closed with strength < 1 */ + + if (config.strength < 1) + { + modal_message_box(NEGATIVE_STRENGTH_TEXT); + } + else + { + dialog_result = 1; + gtk_widget_destroy(GTK_WIDGET(data)); + } + return; +} /* ok_callback */ + +/*****/ + +static void +entry_callback(GtkWidget *widget, gpointer data) +{ + GtkAdjustment *adjustment; + gint new_value; + + new_value = atoi(gtk_entry_get_text(GTK_ENTRY(widget))); + + if (*(gint *) data != new_value) + { + *(gint *) data = new_value; + adjustment = gtk_object_get_user_data(GTK_OBJECT(widget)); + if ((new_value >= adjustment-> lower) + && (new_value <= adjustment->upper)) + { + adjustment->value = new_value; + gtk_signal_handler_block_by_data(GTK_OBJECT(adjustment), data); + gtk_signal_emit_by_name(GTK_OBJECT(adjustment), "value_changed"); + gtk_signal_handler_unblock_by_data(GTK_OBJECT(adjustment), data); + } + } + return; +} /* entry_callback */ + +/*****/ + +static void +radio_button_alg_callback(GtkWidget *widget, gpointer data) +{ + if (GTK_TOGGLE_BUTTON (widget)->active) /* button is TRUE, i.e. checked */ + { + config.alg = (algorithm_t) data; + } + return; +} /* radio_button_alg_callback */ + +/*****/ + +static void +radio_button_direction_callback(GtkWidget *widget, gpointer data) +{ + if (GTK_TOGGLE_BUTTON (widget)->active) /* button is checked */ + { + config.direction = (direction_t) data; + } + return; +} /* radio_button_direction_callback */ + +/*****/ + +static void +radio_button_edge_callback(GtkWidget *widget, gpointer data) +{ + if (GTK_TOGGLE_BUTTON (widget)->active) /* button is selected */ + { + config.edge = (edge_t) data; + } + return; +} /* radio_button_edge_callback */ + +/*****/ + +static void +adjustment_callback(GtkAdjustment *adjustment, gpointer data) +{ + GtkWidget *entry; + gchar buffer[50]; + + if (*(gint *)data != adjustment->value) + { + *(gint *) data = adjustment->value; + entry = gtk_object_get_user_data(GTK_OBJECT(adjustment)); + sprintf(buffer, "%d", *(gint *) data); + gtk_signal_handler_block_by_data(GTK_OBJECT(entry), data); + gtk_entry_set_text(GTK_ENTRY(entry), buffer); + gtk_signal_handler_unblock_by_data(GTK_OBJECT(entry), data); + } + return; +} /* adjustment_callback */ + +/*****/ + +static void +dialog_box(void) +{ + GtkWidget *table; + GtkWidget *outer_table; + GtkObject *adjustment; + GtkWidget *scale; + GtkWidget *rbutton; + GSList *list; + gchar *text_label; + GtkWidget *frame; + GtkWidget *outer_frame; + GtkWidget *dlg; + GtkWidget *button; + GtkWidget *label; + GtkWidget *entry; + gchar buffer[12]; + gchar **argv; + gint argc; + GtkTooltips *tooltips; + GdkColor tips_fg, tips_bg; + + argc = 1; + argv = g_new(gchar *, 1); + argv[0] = g_strdup(PLUG_IN_NAME); + + gtk_init(&argc, &argv); + gtk_rc_parse(gimp_gtkrc()); + + dlg = gtk_dialog_new(); + gtk_window_set_title(GTK_WINDOW(dlg), PLUG_IN_NAME); + gtk_window_position(GTK_WINDOW(dlg), GTK_WIN_POS_MOUSE); + gtk_signal_connect(GTK_OBJECT(dlg), + "destroy", (GtkSignalFunc) close_callback, NULL); + + /* Action area */ + button = gtk_button_new_with_label("OK"); + GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); + gtk_signal_connect(GTK_OBJECT(button), "clicked", + (GtkSignalFunc) ok_callback, dlg); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->action_area), button, + TRUE, TRUE, 0); + gtk_widget_grab_default(button); + gtk_widget_show(button); + + button = gtk_button_new_with_label("Cancel"); + GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); + gtk_signal_connect_object(GTK_OBJECT(button), "clicked", + (GtkSignalFunc) gtk_widget_destroy, + GTK_OBJECT(dlg)); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->action_area), button, + TRUE, TRUE, 0); + gtk_widget_show(button); + + /* init tooltips */ + tooltips = gtk_tooltips_new(); + tips_fg.red = 0; + tips_fg.green = 0; + tips_fg.blue = 0; + gdk_color_alloc(gtk_widget_get_colormap(dlg), &tips_fg); + tips_bg.red = 61669; + tips_bg.green = 59113; + tips_bg.blue = 35979; + gdk_color_alloc(gtk_widget_get_colormap(dlg), &tips_bg); + gtk_tooltips_set_colors(tooltips, &tips_bg, &tips_fg); + + /**************************************************** + frame for sliders + ****************************************************/ + frame = gtk_frame_new(NULL); + gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN); + gtk_container_border_width(GTK_CONTAINER(frame), 10); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->vbox), frame, TRUE, TRUE, 0); + + table = gtk_table_new(3, 2, FALSE); + gtk_container_border_width(GTK_CONTAINER(table), 10); + gtk_container_add(GTK_CONTAINER(frame), table); + + /***************************************************** + slider and entry for threshold + ***************************************************/ + + label = gtk_label_new("Threshold:"); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0); + gtk_widget_show(label); + + adjustment = gtk_adjustment_new(config.threshold, MIN_THRESHOLD, + MAX_THRESHOLD, 1.0, 1.0, 0); + gtk_signal_connect(GTK_OBJECT(adjustment), "value_changed", + (GtkSignalFunc) adjustment_callback, + &config.threshold); + scale = gtk_hscale_new(GTK_ADJUSTMENT(adjustment)); + gtk_widget_set_usize(scale, SCALE_WIDTH, 0); + gtk_table_attach(GTK_TABLE(table), scale, 1, 2, 0, 1, GTK_FILL, 0, 0, 0); + gtk_scale_set_draw_value(GTK_SCALE(scale), FALSE); + gtk_range_set_update_policy(GTK_RANGE(scale), GTK_UPDATE_CONTINUOUS); + gtk_widget_show(scale); + gtk_tooltips_set_tip(tooltips, scale, THRESHOLD_TEXT, NULL); + + entry = gtk_entry_new(); + gtk_object_set_user_data(GTK_OBJECT(entry), adjustment); + gtk_object_set_user_data(GTK_OBJECT(adjustment), entry); + gtk_widget_set_usize(entry, ENTRY_WIDTH, 0); + sprintf(buffer, "%i", config.threshold); + gtk_entry_set_text(GTK_ENTRY(entry), buffer); + gtk_signal_connect(GTK_OBJECT(entry), "changed", + GTK_SIGNAL_FUNC(entry_callback), + (gpointer) &config.threshold); + gtk_table_attach(GTK_TABLE(table), entry, 2, 3, 0, 1, GTK_FILL, 0, 0, 0); + gtk_widget_show(entry); + gtk_tooltips_set_tip(tooltips, entry, THRESHOLD_TEXT, NULL); + + /***************************************************** + slider and entry for strength of wind + ****************************************************/ + + label = gtk_label_new("Strength:"); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, GTK_FILL, 0, 0, 0); + gtk_widget_show(label); + + adjustment = gtk_adjustment_new(config.strength, MIN_STRENGTH, + MAX_STRENGTH, 1.0, 1.0, 0); + gtk_signal_connect(GTK_OBJECT(adjustment), "value_changed", + (GtkSignalFunc) adjustment_callback, + &config.strength); + scale = gtk_hscale_new(GTK_ADJUSTMENT(adjustment)); + gtk_widget_set_usize(scale, SCALE_WIDTH, 0); + gtk_table_attach(GTK_TABLE(table), scale, 1, 2, 1, 2, GTK_FILL, 0, 0, 0); + gtk_scale_set_draw_value(GTK_SCALE(scale), FALSE); + gtk_range_set_update_policy(GTK_RANGE(scale), GTK_UPDATE_CONTINUOUS); + gtk_widget_show(scale); + gtk_tooltips_set_tip(tooltips, scale, STRENGTH_TEXT, NULL); + + entry = gtk_entry_new(); + gtk_object_set_user_data(GTK_OBJECT(entry), adjustment); + gtk_object_set_user_data(GTK_OBJECT(adjustment), entry); + gtk_widget_set_usize(entry, ENTRY_WIDTH, 0); + sprintf(buffer, "%i", config.strength); + gtk_entry_set_text(GTK_ENTRY(entry), buffer); + gtk_signal_connect(GTK_OBJECT(entry), "changed", + GTK_SIGNAL_FUNC(entry_callback), + (gpointer) &config.strength); + gtk_table_attach(GTK_TABLE(table), entry, 2, 3, 1, 2, GTK_FILL, 0, 0, 0); + gtk_widget_show(entry); + gtk_tooltips_set_tip(tooltips, entry, STRENGTH_TEXT, NULL); + + gtk_widget_show(table); + gtk_widget_show(frame); + + /***************************************************** + outer frame and table + ***************************************************/ + + outer_frame = gtk_frame_new(NULL); + gtk_frame_set_shadow_type(GTK_FRAME(outer_frame), GTK_SHADOW_NONE); + gtk_container_border_width(GTK_CONTAINER(outer_frame), 10); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->vbox), outer_frame, + TRUE, TRUE, 0); + + outer_table = gtk_table_new(3, 3, FALSE); + gtk_container_border_width(GTK_CONTAINER(outer_table), 0); + gtk_table_set_col_spacings(GTK_TABLE(outer_table), 10); + gtk_container_add(GTK_CONTAINER(outer_frame), outer_table); + + /********************************************************* + radio buttons for choosing wind rendering algorithm + ******************************************************/ + + frame = gtk_frame_new("Style"); + gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN); + gtk_container_border_width(GTK_CONTAINER(frame), 0); + gtk_table_attach(GTK_TABLE(outer_table), frame, 0, 1, 0, 3, + GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0); + + table = gtk_table_new(3, 2, FALSE); + gtk_container_border_width(GTK_CONTAINER(table), 10); + gtk_container_add(GTK_CONTAINER(frame), table); + + text_label = "Wind"; + rbutton = gtk_radio_button_new_with_label(NULL, text_label); + list = gtk_radio_button_group((GtkRadioButton *) rbutton); + gtk_toggle_button_set_state((GtkToggleButton *) rbutton, + config.alg == RENDER_WIND ? TRUE : FALSE); + gtk_signal_connect(GTK_OBJECT(rbutton), "toggled", + GTK_SIGNAL_FUNC (radio_button_alg_callback), + (gpointer) RENDER_WIND); + gtk_table_attach(GTK_TABLE(table), rbutton, 0, 1, 0, 1, GTK_FILL, 0, 0, 0); + gtk_widget_show(rbutton); + gtk_tooltips_set_tip(tooltips, rbutton, WIND_TEXT, NULL); + + text_label = "Blast"; + rbutton = gtk_radio_button_new_with_label(list, text_label); + list = gtk_radio_button_group((GtkRadioButton *) rbutton); + gtk_toggle_button_set_state((GtkToggleButton *) rbutton, + config.alg == RENDER_BLAST ? TRUE : FALSE); + gtk_signal_connect(GTK_OBJECT(rbutton), "toggled", + GTK_SIGNAL_FUNC (radio_button_alg_callback), + (gpointer) RENDER_BLAST); + gtk_table_attach(GTK_TABLE(table), rbutton, 0, 1, 1, 2, GTK_FILL, 0, 0, 0); + gtk_widget_show(rbutton); + gtk_tooltips_set_tip(tooltips, rbutton, BLAST_TEXT, NULL); + + gtk_widget_show(table); + gtk_widget_show(frame); + + /****************************************************** + radio buttons for choosing LEFT or RIGHT + **************************************************/ + frame = gtk_frame_new("Direction"); + gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN); + gtk_container_border_width(GTK_CONTAINER(frame), 0); + gtk_table_attach(GTK_TABLE(outer_table), frame, 1, 2, 0, 3, + GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0); + + table = gtk_table_new(1, 2, FALSE); + gtk_container_border_width(GTK_CONTAINER(table), 10); + gtk_container_add(GTK_CONTAINER(frame), table); + + rbutton = gtk_radio_button_new_with_label(NULL, "Left"); + list = gtk_radio_button_group((GtkRadioButton *) rbutton); + gtk_toggle_button_set_state((GtkToggleButton *) rbutton, + config.direction == LEFT ? TRUE : FALSE); + gtk_signal_connect(GTK_OBJECT(rbutton), "toggled", + GTK_SIGNAL_FUNC(radio_button_direction_callback), + (gpointer) LEFT); + gtk_table_attach(GTK_TABLE(table), rbutton, 0, 1, 0, 1, GTK_FILL, 0, 0, 0); + gtk_widget_show(rbutton); + gtk_tooltips_set_tip(tooltips, rbutton, LEFT_TEXT, NULL); + + rbutton = gtk_radio_button_new_with_label(list, "Right"); + gtk_toggle_button_set_state((GtkToggleButton *) rbutton, + config.direction == RIGHT ? TRUE : FALSE); + gtk_signal_connect(GTK_OBJECT(rbutton), "toggled", + GTK_SIGNAL_FUNC(radio_button_direction_callback), + (gpointer) RIGHT); + gtk_table_attach(GTK_TABLE(table), rbutton, 0, 1, 1, 2, GTK_FILL, 0, 0, 0); + gtk_widget_show(rbutton); + gtk_tooltips_set_tip(tooltips, rbutton, RIGHT_TEXT, NULL); + + gtk_widget_show(table); + gtk_widget_show(frame); + + /***************************************************** + radio buttons for choosing BOTH, LEADING, TRAILING + ***************************************************/ + + frame = gtk_frame_new("Edge affected"); + gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN); + gtk_container_border_width(GTK_CONTAINER(frame), 0); + gtk_table_attach(GTK_TABLE(outer_table), frame, 2, 3, 0, 3, + GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0); + + table = gtk_table_new(1, 3, FALSE); + gtk_container_border_width(GTK_CONTAINER(table), 10); + gtk_container_add(GTK_CONTAINER(frame), table); + + rbutton = gtk_radio_button_new_with_label(NULL, "Leading"); + list = gtk_radio_button_group((GtkRadioButton *) rbutton); + gtk_toggle_button_set_state((GtkToggleButton *) rbutton, + config.edge == LEADING ? TRUE : FALSE); + gtk_signal_connect(GTK_OBJECT(rbutton), "toggled", + GTK_SIGNAL_FUNC(radio_button_edge_callback), + (gpointer) LEADING); + gtk_table_attach(GTK_TABLE(table), rbutton, 0, 1, 0, 1, GTK_FILL, 0, 0, 0); + gtk_widget_show(rbutton); + gtk_tooltips_set_tip(tooltips, rbutton, LEADING_TEXT, NULL); + + rbutton = gtk_radio_button_new_with_label(list, "Trailing"); + list = gtk_radio_button_group((GtkRadioButton *) rbutton); + gtk_toggle_button_set_state((GtkToggleButton *) rbutton, + config.edge == TRAILING ? TRUE : FALSE); + gtk_signal_connect(GTK_OBJECT(rbutton), "toggled", + GTK_SIGNAL_FUNC(radio_button_edge_callback), + (gpointer) TRAILING); + gtk_table_attach(GTK_TABLE(table), rbutton, 0, 1, 1, 2, GTK_FILL, 0, 0, 0); + gtk_widget_show(rbutton); + gtk_tooltips_set_tip(tooltips, rbutton, TRAILING_TEXT, NULL); + + rbutton = gtk_radio_button_new_with_label(list, "Both"); + gtk_toggle_button_set_state((GtkToggleButton *) rbutton, + config.edge == BOTH ? TRUE : FALSE); + gtk_signal_connect(GTK_OBJECT(rbutton), "toggled", + GTK_SIGNAL_FUNC(radio_button_edge_callback), + (gpointer) BOTH); + gtk_table_attach(GTK_TABLE(table), rbutton, 0, 1, 2, 3, GTK_FILL, 0, 0, 0); + gtk_widget_show(rbutton); + gtk_tooltips_set_tip(tooltips, rbutton, BOTH_TEXT, NULL); + + gtk_widget_show(table); + gtk_widget_show(frame); + + gtk_widget_show(outer_table); + gtk_widget_show(outer_frame); + gtk_widget_show(dlg); + + gtk_main(); + gdk_flush(); + + return; +} + +/*****/ diff --git a/plug-ins/jigsaw/.cvsignore b/plug-ins/jigsaw/.cvsignore new file mode 100644 index 0000000000..49f4fa6a10 --- /dev/null +++ b/plug-ins/jigsaw/.cvsignore @@ -0,0 +1,6 @@ +Makefile.in +Makefile +.deps +_libs +.libs +jigsaw diff --git a/plug-ins/jigsaw/Makefile.am b/plug-ins/jigsaw/Makefile.am new file mode 100644 index 0000000000..6af27e9adb --- /dev/null +++ b/plug-ins/jigsaw/Makefile.am @@ -0,0 +1,35 @@ +## Process this file with automake to produce Makefile.in + +pluginlibdir = $(gimpplugindir)/plug-ins + +pluginlib_PROGRAMS = jigsaw + +jigsaw_SOURCES = \ + jigsaw.c + +INCLUDES = \ + -I$(top_srcdir) \ + $(GTK_CFLAGS) \ + -I$(includedir) + +LDADD = \ + $(top_builddir)/libgimp/libgimp.la \ + $(GTK_LIBS) + +DEPS = \ + $(top_builddir)/libgimp/libgimp.la + +jigsaw_DEPENDENCIES = $(DEPS) + +.PHONY: files + +files: + @files=`ls $(DISTFILES) 2> /dev/null`; for p in $$files; do \ + echo $$p; \ + done + @for subdir in $(SUBDIRS); do \ + files=`cd $$subdir; $(MAKE) files | grep -v "make\[[1-9]\]"`; \ + for file in $$files; do \ + echo $$subdir/$$file; \ + done; \ + done diff --git a/plug-ins/jigsaw/jigsaw.c b/plug-ins/jigsaw/jigsaw.c new file mode 100644 index 0000000000..b9aa183a6e --- /dev/null +++ b/plug-ins/jigsaw/jigsaw.c @@ -0,0 +1,2708 @@ +/* + * jigsaw - a plug-in for the GIMP + * + * Copyright (C) Nigel Wetten + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Contact info: nigel@cs.nwu.edu + * + * Version: 1.0.0 + */ + +#include +#include + +#include +#include "libgimp/gimp.h" + +typedef enum {BEZIER_1, BEZIER_2} style_t; +typedef enum {LEFT, RIGHT, UP, DOWN} bump_t; + + +static void query(void); +static void run(gchar *name, gint nparams, GParam *param, + gint *nreturn_vals, GParam **return_vals); +static gint jigsaw(void); +static void run_callback(GtkWidget *widget, gpointer data); +static void dialog_box(void); +static void dialog_close_callback(GtkWidget *widget, gpointer data); +static void entry_callback(GtkWidget *widget, gpointer data); +static void entry_double_callback(GtkWidget *widget, gpointer data); +static void radio_button_primitive_callback(GtkWidget *widget, gpointer data); +static void adjustment_callback(GtkAdjustment *adjustment, gpointer data); +static void adjustment_double_callback(GtkAdjustment *adjustment, + gpointer data); +static void check_button_callback(GtkWidget *widget, gpointer data); + +static void draw_jigsaw(guchar *buffer, gint width, gint height, gint bytes); +static void draw_vertical_border(guchar *buffer, gint width, gint height, + gint bytes, gint x_offset, gint ytiles, + gint blend_lines, gdouble blend_amount); +static void draw_horizontal_border(guchar *buffer, gint width, + gint bytes, gint y_offset, gint xtiles, + gint blend_lines, gdouble blend_amount); +static void draw_vertical_line(guchar *buffer, gint width, gint bytes, + gint px[2], gint py[2]); +static void draw_horizontal_line(guchar *buffer, gint width, gint bytes, + gint px[2], gint py[2]); +static void darken_vertical_line(guchar *buffer, gint width, gint bytes, + gint *px, gint *py, gdouble delta); +static void lighten_vertical_line(guchar *buffer, gint width, gint bytes, + gint *px, gint *py, gdouble delta); +static void darken_horizontal_line(guchar *buffer, gint width, gint bytes, + gint *px, gint *py, gdouble delta); +static void lighten_horizontal_line(guchar *buffer, gint width, gint bytes, + gint *px, gint *py, gdouble delta); +static void draw_right_bump(guchar *buffer, gint width, gint bytes, + gint x_offset, gint curve_start_offset, + gint steps); +static void draw_left_bump(guchar *buffer, gint width, gint bytes, + gint x_offset, gint curve_start_offset, + gint steps); +static void draw_up_bump(guchar *buffer, gint width, gint bytes, + gint y_offset, gint curve_start_offset, + gint steps); +static void draw_down_bump(guchar *buffer, gint width, gint bytes, + gint y_offset, gint curve_start_offset, + gint steps); +static void darken_right_bump(guchar *buffer, gint width, gint bytes, + gint x_offset, gint curve_start_offset, + gint steps, gdouble delta, gint counter); +static void lighten_right_bump(guchar *buffer, gint width, gint bytes, + gint x_offset, gint curve_start_offset, + gint steps, gdouble delta, gint counter); +static void darken_left_bump(guchar *buffer, gint width, gint bytes, + gint x_offset, gint curve_start_offset, + gint steps, gdouble delta, gint counter); +static void lighten_left_bump(guchar *buffer, gint width, gint bytes, + gint x_offset, gint curve_start_offset, + gint steps, gdouble delta, gint counter); +static void darken_up_bump(guchar *buffer, gint width, gint bytes, + gint y_offset, gint curve_start_offest, + gint steps, gdouble delta, gint counter); +static void lighten_up_bump(guchar *buffer, gint width, gint bytes, + gint y_offset, gint curve_start_offset, + gint steps, gdouble delta, gint counter); +static void darken_down_bump(guchar *buffer, gint width, gint bytes, + gint y_offset, gint curve_start_offset, + gint steps, gdouble delta, gint counter); +static void lighten_down_bump(guchar *buffer, gint width, gint bytes, + gint y_offset, gint curve_start_offset, + gint steps, gdouble delta, gint counter); +static void generate_grid(gint width, gint height, gint xtiles, gint ytiles, + gint *x, gint *y); +static void generate_bezier(gint px[4], gint py[4], gint steps, + gint *cachex, gint *cachey); +static void malloc_cache(void); +static void free_cache(void); +static void init_right_bump(gint width, gint height); +static void init_left_bump(gint width, gint height); +static void init_up_bump(gint width, gint height); +static void init_down_bump(gint width, gint height); +static void draw_bezier_line(guchar *buffer, gint width, gint bytes, + gint steps, gint *cx, gint *cy); +static void darken_bezier_line(guchar *buffer, gint width, gint bytes, + gint x_offset, gint y_offset, gint steps, + gint *cx, gint *cy, gdouble delta); +static void lighten_bezier_line(guchar *buffer, gint width, gint bytes, + gint x_offset, gint y_offset, gint steps, + gint *cx, gint *cy, gdouble delta); +static void draw_bezier_vertical_border(guchar *buffer, gint width, + gint height, gint bytes, + gint x_offset, gint xtiles, + gint ytiles, gint blend_lines, + gdouble blend_amount, gint steps); +static void draw_bezier_horizontal_border(guchar *buffer, gint width, + gint height, gint bytes, + gint x_offset, gint xtiles, + gint ytiles, gint blend_lines, + gdouble blend_amount, gint steps); +static void check_config(gint width, gint height); + + + +#define PLUG_IN_NAME "jigsaw" +#define PLUG_IN_STORAGE "jigsaw-storage" + +#define XFACTOR2 0.0833 +#define XFACTOR3 0.2083 +#define XFACTOR4 0.2500 + +#define XFACTOR5 0.2500 +#define XFACTOR6 0.2083 +#define XFACTOR7 0.0833 + +#define YFACTOR2 0.1000 +#define YFACTOR3 0.2200 +#define YFACTOR4 0.1000 + +#define YFACTOR5 0.1000 +#define YFACTOR6 0.4666 +#define YFACTOR7 0.1000 +#define YFACTOR8 0.2000 + +#define MAX_VALUE 255 +#define MIN_VALUE 0 +#define DELTA 0.15 + +#define BLACK_R 30 +#define BLACK_G 30 +#define BLACK_B 30 + +#define WALL_XFACTOR2 0.05 +#define WALL_XFACTOR3 0.05 +#define WALL_YFACTOR2 0.05 +#define WALL_YFACTOR3 0.05 + +#define WALL_XCONS2 0.2 +#define WALL_XCONS3 0.3 +#define WALL_YCONS2 0.2 +#define WALL_YCONS3 0.3 + +#define FUDGE 1.2 + +#define MIN_XTILES 1 +#define MAX_XTILES 20 +#define MIN_YTILES 1 +#define MAX_YTILES 20 +#define MIN_BLEND_LINES 0 +#define MAX_BLEND_LINES 10 +#define MIN_BLEND_AMOUNT 0 +#define MAX_BLEND_AMOUNT 1.0 + +#define SCALE_WIDTH 200 +#define ENTRY_WIDTH 40 + +#define XTILES_TEXT "Number of pieces going across" +#define YTILES_TEXT "Number of pieces going down" +#define BLEND_LINES_TEXT "Degree of slope of each piece's edge" +#define BLEND_AMOUNT_TEXT "The amount of highlighting on the edges of each piece" +#define SQUARE_TEXT "Each piece has straight sides" +#define CURVE_TEXT "Each piece has curved sides" +#define DISABLE_TEXT "Toggle Tooltips on/off" + +#define DRAW_POINT(buffer, index) \ +do { \ + buffer[index] = BLACK_R; \ + buffer[index+1] = BLACK_G; \ + buffer[index+2] = BLACK_B; \ +} while (0) + +#define DARKEN_POINT(buffer, index, delta, temp) \ +do { \ + temp = buffer[index]; \ + temp -= buffer[index] * delta; \ + if (temp < MIN_VALUE) temp = MIN_VALUE; \ + buffer[index] = temp; \ + temp = buffer[index+1]; \ + temp -= buffer[index+1] * delta; \ + if (temp < MIN_VALUE) temp = MIN_VALUE; \ + buffer[index+1] = temp; \ + temp = buffer[index+2]; \ + temp -= buffer[index+2] * delta; \ + if (temp < MIN_VALUE) temp = MIN_VALUE; \ + buffer[index+2] = temp; \ +} while (0) + +#define LIGHTEN_POINT(buffer, index, delta, temp) \ +do { \ + temp = buffer[index] * delta; \ + temp += buffer[index]; \ + if (temp > MAX_VALUE) temp = MAX_VALUE; \ + buffer[index] = temp; \ + temp = buffer[index+1] * delta; \ + temp += buffer[index+1]; \ + if (temp > MAX_VALUE) temp = MAX_VALUE; \ + buffer[index+1] = temp; \ + temp = buffer[index+2] * delta; \ + temp += buffer[index+2]; \ + if (temp > MAX_VALUE) temp = MAX_VALUE; \ + buffer[index+2] = temp; \ +} while (0) + + +GPlugInInfo PLUG_IN_INFO = +{ + NULL, + NULL, + query, + run +}; + +struct config_tag +{ + gint x; + gint y; + style_t style; + gint blend_lines; + gdouble blend_amount; +}; + + +typedef struct config_tag config_t; + +static config_t config = +{ + 5, + 5, + BEZIER_1, + 3, + 0.5 +}; + +struct globals_tag +{ + GDrawable *drawable; + gint *cachex1[4]; + gint *cachex2[4]; + gint *cachey1[4]; + gint *cachey2[4]; + gint steps[4]; + gint *gridx; + gint *gridy; + gint **blend_outer_cachex1[4]; + gint **blend_outer_cachex2[4]; + gint **blend_outer_cachey1[4]; + gint **blend_outer_cachey2[4]; + gint **blend_inner_cachex1[4]; + gint **blend_inner_cachex2[4]; + gint **blend_inner_cachey1[4]; + gint **blend_inner_cachey2[4]; + gint dialog_result; + gint tooltips; +}; + +typedef struct globals_tag globals_t; + +static globals_t globals = +{ + 0, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + 0, + 0, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + -1, + 1 +}; + +MAIN() + +static void +query(void) +{ + static GParamDef args[] = + { + { PARAM_INT32, "run_mode", "Interactive, Non-interactive, Last-Vals"}, + { PARAM_IMAGE, "image", "Input image"}, + { PARAM_DRAWABLE, "drawable", "Input drawable"}, + { PARAM_INT32, "x", "Number of tiles across > 0"}, + { PARAM_INT32, "y", "Number of tiles down > 0"}, + { PARAM_INT32, "style", "The style/shape of the jigsaw puzzle, 0 or 1"}, + { PARAM_INT32, "blend_lines", "Number of lines for shading bevels >= 0"}, + { PARAM_FLOAT, "blend_amount", "The power of the light highlights 0 =< 5"} + }; + static GParamDef *return_vals = NULL; + static gint nargs = sizeof(args) / sizeof(args[0]); + static gint nreturn_vals = 0; + + gimp_install_procedure("plug_in_jigsaw", + "Renders a jigsaw puzzle look", + "Jigsaw puzzle look", + "Nigel Wetten", + "Nigel Wetten", + "1998", + "/Filters/Render/Jigsaw", + "RGB*", + PROC_PLUG_IN, + nargs, nreturn_vals, + args, return_vals); + + return; +} /* query */ + +/*****/ + +static void +run(gchar *name, gint nparams, GParam *param, gint *nreturn_vals, + GParam **return_vals) +{ + static GParam values[1]; + GDrawable *drawable; + GRunModeType run_mode; + GStatusType status = STATUS_SUCCESS; + + run_mode = param[0].data.d_int32; + drawable = gimp_drawable_get(param[2].data.d_drawable); + globals.drawable = drawable; + gimp_tile_cache_ntiles(drawable->width / gimp_tile_width() + 1); + switch (run_mode) + { + case RUN_NONINTERACTIVE: + if (nparams == 8) + { + config.x = param[3].data.d_int32; + config.y = param[4].data.d_int32; + config.style = param[5].data.d_int32; + config.blend_lines = param[6].data.d_int32; + config.blend_amount = param[7].data.d_float; + if (jigsaw() == -1) + { + status = STATUS_EXECUTION_ERROR; + } + } + else + { + status = STATUS_CALLING_ERROR; + } + break; + + case RUN_INTERACTIVE: + gimp_get_data("plug_in_jigsaw", &config); + gimp_get_data(PLUG_IN_STORAGE, &globals.tooltips); + dialog_box(); + if (globals.dialog_result == -1) + { + status = STATUS_EXECUTION_ERROR; + break; + } + gimp_progress_init("Assembling Jigsaw"); + if (jigsaw() == -1) + { + status = STATUS_CALLING_ERROR; + break; + } + gimp_set_data("plug_in_jigsaw", &config, sizeof(config_t)); + gimp_set_data(PLUG_IN_STORAGE, &globals.tooltips, + sizeof(globals.tooltips)); + gimp_displays_flush(); + break; + + case RUN_WITH_LAST_VALS: + gimp_get_data("plug_in_jigsaw", &config); + if (jigsaw() == -1) + { + status = STATUS_EXECUTION_ERROR; + gimp_message("An execution error occured."); + } + else + { + gimp_displays_flush(); + } + + } /* switch */ + + gimp_drawable_detach(drawable); + + *nreturn_vals = 1; + *return_vals = values; + values[0].type = PARAM_STATUS; + values[0].data.d_status = status; + + return; +} /* run */ + +/*****/ + +static gint +jigsaw(void) +{ + GPixelRgn src_pr, dest_pr; + guchar *buffer; + GDrawable *drawable = globals.drawable; + gint width = drawable->width; + gint height = drawable->height; + gint bytes = drawable->bpp; + gint buffer_size = bytes * width * height; + + srand((gint)NULL); + + + /* setup image buffer */ + gimp_pixel_rgn_init(&src_pr, drawable, 0, 0, width, height, FALSE, FALSE); + gimp_pixel_rgn_init(&dest_pr, drawable, 0, 0, width, height, TRUE, TRUE); + buffer = g_malloc(buffer_size); + gimp_pixel_rgn_get_rect(&src_pr, buffer, 0, 0, width, height); + + check_config(width, height); + globals.steps[LEFT] = globals.steps[RIGHT] = globals.steps[UP] + = globals.steps[DOWN] = (config.x < config.y) ? + (width / config.x * 2) : (height / config.y * 2); + + malloc_cache(); + draw_jigsaw(buffer, width, height, bytes); + free_cache(); + /* cleanup */ + gimp_pixel_rgn_set_rect(&dest_pr, buffer, 0, 0, width, height); + gimp_drawable_flush(drawable); + gimp_drawable_merge_shadow(drawable->id, TRUE); + gimp_drawable_update(drawable->id, 0, 0, width, height); + g_free(buffer); + + return 0; +} /* jigsaw */ + +/*****/ + +static void +generate_bezier(gint px[4], gint py[4], gint steps, + gint *cachex, gint *cachey) +{ + gdouble t = 0.0; + gdouble sigma = 1.0 / steps; + gint i; + + for (i = 0; i < steps; i++) + { + gdouble t2, t3, x, y, t_1; + + t += sigma; + t2 = t * t; + t3 = t2 * t; + t_1 = 1 - t; + x = t_1 * t_1 * t_1 * px[0] + + 3 * t * t_1 * t_1 * px[1] + + 3 * t2 * t_1 * px[2] + + t3 * px[3]; + y = t_1 * t_1 * t_1 * py[0] + + 3 * t * t_1 * t_1 * py[1] + + 3 * t2 * t_1 * py[2] + + t3 * py[3]; + cachex[i] = (gint) (x + 0.2); + cachey[i] = (gint) (y + 0.2); + } /* for */ + return; +} /* generate_bezier */ + +/*****/ + +static void +draw_jigsaw(guchar *buffer, gint width, gint height, gint bytes) +{ + gint i; + gint *x, *y; + gint xtiles = config.x; + gint ytiles = config.y; + gint xlines = xtiles - 1; + gint ylines = ytiles - 1; + gint blend_lines = config.blend_lines; + gdouble blend_amount = config.blend_amount; + gint steps = globals.steps[RIGHT]; + style_t style = config.style; + gint progress_total = xlines + ylines - 1; + + globals.gridx = g_malloc(sizeof(gint) * xtiles); + globals.gridy = g_malloc(sizeof(gint) * ytiles); + x = globals.gridx; + y = globals.gridy; + + generate_grid(width, height, xtiles, ytiles, globals.gridx, globals.gridy); + + init_right_bump(width, height); + init_left_bump(width, height); + init_up_bump(width, height); + init_down_bump(width, height); + + if (style == BEZIER_1) + { + for (i = 0; i < xlines; i++) + { + draw_vertical_border(buffer, width, height, bytes, x[i], ytiles, + blend_lines, blend_amount); + gimp_progress_update((gdouble) i / (gdouble) progress_total); + } + for (i = 0; i < ylines; i++) + { + draw_horizontal_border(buffer, width, bytes, y[i], xtiles, + blend_lines, blend_amount); + gimp_progress_update((gdouble) (i + xlines) + / (gdouble) progress_total); + } + } + else if (style == BEZIER_2) + { + for (i = 0; i < xlines; i++) + { + draw_bezier_vertical_border(buffer, width, height, bytes, x[i], + xtiles, ytiles, blend_lines, + blend_amount, steps); + gimp_progress_update((gdouble) i / (gdouble) progress_total); + } + for (i = 0; i < ylines; i++) + { + draw_bezier_horizontal_border(buffer, width, height, bytes, y[i], + xtiles, ytiles, blend_lines, + blend_amount, steps); + gimp_progress_update((gdouble) (i + xlines) + / (gdouble) progress_total); + } + } + else + { + printf("draw_jigsaw: bad style\n"); + exit(1); + } + g_free(globals.gridx); + g_free(globals.gridy); + + return; +} /* draw_jigsaw */ + +/*****/ + +static void +draw_vertical_border(guchar *buffer, gint width, gint height, gint bytes, + gint x_offset, gint ytiles, gint blend_lines, + gdouble blend_amount) +{ + gint i, j; + gint tile_height = height / ytiles; + gint tile_height_eighth = tile_height / 8; + gint curve_start_offset = 3 * tile_height_eighth; + gint curve_end_offset = curve_start_offset + 2 * tile_height_eighth; + gint px[2], py[2]; + gint ly[2], dy[2]; + gint y_offset = 0; + gdouble delta; + gdouble sigma = blend_amount / blend_lines; + gint right; + bump_t style_index; + + for (i = 0; i < ytiles; i++) + { + right = rand() & 1; + if (right) + { + style_index = RIGHT; + } + else + { + style_index = LEFT; + } + + /* first straight line from top downwards */ + px[0] = px[1] = x_offset; + py[0] = y_offset; py[1] = y_offset + curve_start_offset - 1; + draw_vertical_line(buffer, width, bytes, px, py); + delta = blend_amount; + dy[0] = ly[0] = py[0]; dy[1] = ly[1] = py[1]; + if (!right) + { + ly[1] += blend_lines + 2; + } + for (j = 0; j < blend_lines; j++) + { + dy[0]++; dy[1]--; ly[0]++; ly[1]--; + px[0] = x_offset - j - 1; + darken_vertical_line(buffer, width, bytes, px, dy, delta); + px[0] = x_offset + j + 1; + lighten_vertical_line(buffer, width, bytes, px, ly, delta); + delta -= sigma; + } + + /* top half of curve */ + if (right) + { + draw_right_bump(buffer, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[RIGHT]); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + /* use to be -j -1 */ + darken_right_bump(buffer, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[RIGHT], delta, j); + /* use to be +j + 1 */ + lighten_right_bump(buffer, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[RIGHT], delta, j); + delta -= sigma; + } + } + else + { + draw_left_bump(buffer, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[LEFT]); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + /* use to be -j -1 */ + darken_left_bump(buffer, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[LEFT], delta, j); + /* use to be -j - 1 */ + lighten_left_bump(buffer, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[LEFT], delta, j); + delta -= sigma; + } + } + /* bottom straight line of a tile wall */ + px[0] = px[1] = x_offset; + py[0] = y_offset + curve_end_offset; + py[1] = globals.gridy[i]; + draw_vertical_line(buffer, width, bytes, px, py); + delta = blend_amount; + dy[0] = ly[0] = py[0]; dy[1] = ly[1] = py[1]; + if (right) + { + dy[0] -= blend_lines + 2; + } + for (j = 0; j < blend_lines; j++) + { + dy[0]++; dy[1]--; ly[0]++; ly[1]--; + px[0] = x_offset - j - 1; + darken_vertical_line(buffer, width, bytes, px, dy, delta); + px[0] = x_offset + j + 1; + lighten_vertical_line(buffer, width, bytes, px, ly, delta); + delta -= sigma; + } + + y_offset = globals.gridy[i]; + } /* for */ + + return; +} /* draw_vertical_border */ + +/*****/ + +/* assumes RGB* */ +static void +draw_horizontal_border(guchar *buffer, gint width, gint bytes, + gint y_offset, gint xtiles, gint blend_lines, + gdouble blend_amount) +{ + gint i, j; + gint tile_width = width / xtiles; + gint tile_width_eighth = tile_width / 8; + gint curve_start_offset = 3 * tile_width_eighth; + gint curve_end_offset = curve_start_offset + 2 * tile_width_eighth; + gint px[2], py[2]; + gint dx[2], lx[2]; + gint x_offset = 0; + gdouble delta; + gdouble sigma = blend_amount / blend_lines; + gint up; + + for (i = 0; i < xtiles; i++) + { + up = rand() & 1; + /* first horizontal line across */ + px[0] = x_offset; px[1] = x_offset + curve_start_offset - 1; + py[0] = py[1] = y_offset; + draw_horizontal_line(buffer, width, bytes, px, py); + delta = blend_amount; + dx[0] = lx[0] = px[0]; dx[1] = lx[1] = px[1]; + if (up) + { + lx[1] += blend_lines + 2; + } + for (j = 0; j < blend_lines; j++) + { + dx[0]++; dx[1]--; lx[0]++; lx[1]--; + py[0] = y_offset - j - 1; + darken_horizontal_line(buffer, width, bytes, dx, py, delta); + py[0] = y_offset + j + 1; + lighten_horizontal_line(buffer, width, bytes, lx, py, delta); + delta -= sigma; + } + + if (up) + { + draw_up_bump(buffer, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[UP]); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + /* use to be -j -1 */ + darken_up_bump(buffer, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[UP], delta, j); + /* use to be +j + 1 */ + lighten_up_bump(buffer, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[UP], delta, j); + delta -= sigma; + } + } + else + { + draw_down_bump(buffer, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[DOWN]); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + /* use to be +j + 1 */ + darken_down_bump(buffer, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[DOWN], delta, j); + /* use to be -j -1 */ + lighten_down_bump(buffer, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[DOWN], delta, j); + delta -= sigma; + } + } + /* right horizontal line of tile */ + px[0] = x_offset + curve_end_offset; + px[1] = globals.gridx[i]; + py[0] = py[1] = y_offset; + draw_horizontal_line(buffer, width, bytes, px, py); + delta = blend_amount; + dx[0] = lx[0] = px[0]; dx[1] = lx[1] = px[1]; + if (!up) + { + dx[0] -= blend_lines + 2; + } + for (j = 0;j < blend_lines; j++) + { + dx[0]++; dx[1]--; lx[0]++; lx[1]--; + py[0] = y_offset - j - 1; + darken_horizontal_line(buffer, width, bytes, dx, py, delta); + py[0] = y_offset + j + 1; + lighten_horizontal_line(buffer, width, bytes, lx, py, delta); + delta -= sigma; + } + x_offset = globals.gridx[i]; + } /* for */ + + return; +} /* draw_horizontal_border */ + +/*****/ + +/* assumes going top to bottom */ +static void +draw_vertical_line(guchar *buffer, gint width, gint bytes, + gint px[2], gint py[2]) +{ + gint i; + gint rowstride = bytes * width; + gint index = px[0] * bytes + rowstride * py[0]; + gint length = py[1] - py[0] + 1; + + for (i = 0; i < length; i++) + { + DRAW_POINT(buffer, index); + index += rowstride; + } + return; +} /* draw_vertical_line */ + +/*****/ + +/* assumes going left to right */ +static void +draw_horizontal_line(guchar *buffer, gint width, gint bytes, + gint px[2], gint py[2]) +{ + gint i; + gint rowstride = bytes * width; + gint index = px[0] * bytes + py[0] * rowstride; + gint length = px[1] - px[0] + 1; + + for (i = 0; i < length; i++) + { + DRAW_POINT(buffer, index); + index += bytes; + } + return; +} /* draw_horizontal_line */ + +/*****/ + +static void +draw_right_bump(guchar *buffer, gint width, gint bytes, gint x_offset, + gint curve_start_offset, gint steps) +{ + gint i; + gint x, y; + gint index; + gint rowstride = width * bytes; + + for (i = 0; i < steps; i++) + { + x = *(globals.cachex1[RIGHT] + i) + x_offset; + y = *(globals.cachey1[RIGHT] + i) + curve_start_offset; + index = y * rowstride + x * bytes; + DRAW_POINT(buffer, index); + + x = *(globals.cachex2[RIGHT] + i) + x_offset; + y = *(globals.cachey2[RIGHT] + i) + curve_start_offset; + index = y * rowstride + x * bytes; + DRAW_POINT(buffer, index); + } + return; +} /* draw_right_bump */ + +/*****/ + +static void +draw_left_bump(guchar *buffer, gint width, gint bytes, gint x_offset, + gint curve_start_offset, gint steps) +{ + gint i; + gint x, y; + gint index; + gint rowstride = width * bytes; + + for (i = 0; i < steps; i++) + { + x = *(globals.cachex1[LEFT] + i) + x_offset; + y = *(globals.cachey1[LEFT] + i) + curve_start_offset; + index = y * rowstride + x * bytes; + DRAW_POINT(buffer, index); + + x = *(globals.cachex2[LEFT] + i) + x_offset; + y = *(globals.cachey2[LEFT] + i) + curve_start_offset; + index = y * rowstride + x * bytes; + DRAW_POINT(buffer, index); + } + return; +} /* draw_left_bump */ + +/*****/ + +static void +draw_up_bump(guchar *buffer, gint width, gint bytes, gint y_offset, + gint curve_start_offset, gint steps) +{ + gint i; + gint x, y; + gint index; + gint rowstride = width * bytes; + + for (i = 0; i < steps; i++) + { + x = *(globals.cachex1[UP] + i) + curve_start_offset; + y = *(globals.cachey1[UP] + i) + y_offset; + index = y * rowstride + x * bytes; + DRAW_POINT(buffer, index); + + x = *(globals.cachex2[UP] + i) + curve_start_offset; + y = *(globals.cachey2[UP] + i) + y_offset; + index = y * rowstride + x * bytes; + DRAW_POINT(buffer, index); + } + return; +} /* draw_up_bump */ + +/*****/ + +static void +draw_down_bump(guchar *buffer, gint width, gint bytes, gint y_offset, + gint curve_start_offset, gint steps) +{ + gint i; + gint x, y; + gint index; + gint rowstride = width * bytes; + + for (i = 0; i < steps; i++) + { + x = *(globals.cachex1[DOWN] + i) + curve_start_offset; + y = *(globals.cachey1[DOWN] + i) + y_offset; + index = y * rowstride + x * bytes; + DRAW_POINT(buffer, index); + + x = *(globals.cachex2[DOWN] + i) + curve_start_offset; + y = *(globals.cachey2[DOWN] + i) + y_offset; + index = y * rowstride + x * bytes; + DRAW_POINT(buffer, index); + } + return; +} /* draw_down_bump */ + +/*****/ + +static void +malloc_cache(void) +{ + gint i, j; + gint blend_lines = config.blend_lines; + gint length = blend_lines * sizeof(gint *); + + for (i = 0; i < 4; i++) + { + gint steps_length = globals.steps[i] * sizeof(gint); + + globals.cachex1[i] = g_malloc(steps_length); + globals.cachex2[i] = g_malloc(steps_length); + globals.cachey1[i] = g_malloc(steps_length); + globals.cachey2[i] = g_malloc(steps_length); + globals.blend_outer_cachex1[i] = g_malloc(length); + globals.blend_outer_cachex2[i] = g_malloc(length); + globals.blend_outer_cachey1[i] = g_malloc(length); + globals.blend_outer_cachey2[i] = g_malloc(length); + globals.blend_inner_cachex1[i] = g_malloc(length); + globals.blend_inner_cachex2[i] = g_malloc(length); + globals.blend_inner_cachey1[i] = g_malloc(length); + globals.blend_inner_cachey2[i] = g_malloc(length); + for (j = 0; j < blend_lines; j++) + { + *(globals.blend_outer_cachex1[i] + j) = g_malloc(steps_length); + *(globals.blend_outer_cachex2[i] + j) = g_malloc(steps_length); + *(globals.blend_outer_cachey1[i] + j) = g_malloc(steps_length); + *(globals.blend_outer_cachey2[i] + j) = g_malloc(steps_length); + *(globals.blend_inner_cachex1[i] + j) = g_malloc(steps_length); + *(globals.blend_inner_cachex2[i] + j) = g_malloc(steps_length); + *(globals.blend_inner_cachey1[i] + j) = g_malloc(steps_length); + *(globals.blend_inner_cachey2[i] + j) = g_malloc(steps_length); + } + } /* for */ + return; +} /* malloc_cache() */ + +/*****/ + +static void +free_cache(void) +{ + gint i, j; + gint blend_lines = config.blend_lines; + + for (i = 0; i < 4; i ++) + { + g_free(globals.cachex1[i]); + g_free(globals.cachex2[i]); + g_free(globals.cachey1[i]); + g_free(globals.cachey2[i]); + for (j = 0; j < blend_lines; j++) + { + g_free(*(globals.blend_outer_cachex1[i] + j)); + g_free(*(globals.blend_outer_cachex2[i] + j)); + g_free(*(globals.blend_outer_cachey1[i] + j)); + g_free(*(globals.blend_outer_cachey2[i] + j)); + g_free(*(globals.blend_inner_cachex1[i] + j)); + g_free(*(globals.blend_inner_cachex2[i] + j)); + g_free(*(globals.blend_inner_cachey1[i] + j)); + g_free(*(globals.blend_inner_cachey2[i] + j)); + } + g_free(globals.blend_outer_cachex1[i]); + g_free(globals.blend_outer_cachex2[i]); + g_free(globals.blend_outer_cachey1[i]); + g_free(globals.blend_outer_cachey2[i]); + g_free(globals.blend_inner_cachex1[i]); + g_free(globals.blend_inner_cachex2[i]); + g_free(globals.blend_inner_cachey1[i]); + g_free(globals.blend_inner_cachey2[i]); + } /* for */ + return; +} /* free_cache */ + +/*****/ + +static void +init_right_bump(gint width, gint height) +{ + gint i; + gint xtiles = config.x; + gint ytiles = config.y; + gint steps = globals.steps[RIGHT]; + gint px[4], py[4]; + gint x_offset = 0; + gint tile_width = width / xtiles; + gint tile_height = height/ ytiles; + gint tile_height_eighth = tile_height / 8; + gint curve_start_offset = 0; + gint curve_end_offset = curve_start_offset + 2 * tile_height_eighth; + gint blend_lines = config.blend_lines; + + px[0] = x_offset; + px[1] = x_offset + XFACTOR2 * tile_width; + px[2] = x_offset + XFACTOR3 * tile_width; + px[3] = x_offset + XFACTOR4 * tile_width; + py[0] = curve_start_offset; + py[1] = curve_start_offset + YFACTOR2 * tile_height; + py[2] = curve_start_offset - YFACTOR3 * tile_height; + py[3] = curve_start_offset + YFACTOR4 * tile_height; + generate_bezier(px, py, steps, globals.cachex1[RIGHT], + globals.cachey1[RIGHT]); + /* outside right bump */ + for (i = 0; i < blend_lines; i++) + { + py[0]--; py[1]--; py[2]--; px[3]++; + generate_bezier(px, py, steps, + *(globals.blend_outer_cachex1[RIGHT] + i), + *(globals.blend_outer_cachey1[RIGHT] + i)); + } + /* inside right bump */ + py[0] += blend_lines; py[1] += blend_lines; py[2] += blend_lines; + px[3] -= blend_lines; + for (i = 0; i < blend_lines; i++) + { + py[0]++; py[1]++; py[2]++; px[3]--; + generate_bezier(px, py, steps, + *(globals.blend_inner_cachex1[RIGHT] + i), + *(globals.blend_inner_cachey1[RIGHT] + i)); + } + + /* bottom half of bump */ + px[0] = x_offset + XFACTOR5 * tile_width; + px[1] = x_offset + XFACTOR6 * tile_width; + px[2] = x_offset + XFACTOR7 * tile_width; + px[3] = x_offset; + py[0] = curve_start_offset + YFACTOR5 * tile_height; + py[1] = curve_start_offset + YFACTOR6 * tile_height; + py[2] = curve_start_offset + YFACTOR7 * tile_height; + py[3] = curve_end_offset; + generate_bezier(px, py, steps, globals.cachex2[RIGHT], + globals.cachey2[RIGHT]); + /* outer right bump */ + for (i = 0; i < blend_lines; i++) + { + py[1]++; py[2]++; py[3]++; px[0]++; + generate_bezier(px, py, steps, + *(globals.blend_outer_cachex2[RIGHT] + i), + *(globals.blend_outer_cachey2[RIGHT] + i)); + } + /* inner right bump */ + py[1] -= blend_lines; py[2] -= blend_lines; py[3] -= blend_lines; + px[0] -= blend_lines; + for (i = 0; i < blend_lines; i++) + { + py[1]--; py[2]--; py[3]--; px[0]--; + generate_bezier(px, py, steps, + *(globals.blend_inner_cachex2[RIGHT] + i), + *(globals.blend_inner_cachey2[RIGHT] + i)); + } + return; +} /* init_right_bump */ + +/*****/ + +static void +init_left_bump(gint width, gint height) +{ + gint i; + gint xtiles = config.x; + gint ytiles = config.y; + gint steps = globals.steps[LEFT]; + gint px[4], py[4]; + gint x_offset = 0; + gint tile_width = width / xtiles; + gint tile_height = height / ytiles; + gint tile_height_eighth = tile_height / 8; + gint curve_start_offset = 0; + gint curve_end_offset = curve_start_offset + 2 * tile_height_eighth; + gint blend_lines = config.blend_lines; + + px[0] = x_offset; + px[1] = x_offset - XFACTOR2 * tile_width; + px[2] = x_offset - XFACTOR3 * tile_width; + px[3] = x_offset - XFACTOR4 * tile_width; + py[0] = curve_start_offset; + py[1] = curve_start_offset + YFACTOR2 * tile_height; + py[2] = curve_start_offset - YFACTOR3 * tile_height; + py[3] = curve_start_offset + YFACTOR4 * tile_height; + generate_bezier(px, py, steps, globals.cachex1[LEFT], + globals.cachey1[LEFT]); + /* outer left bump */ + for (i = 0; i < blend_lines; i++) + { + py[0]--; py[1]--; py[2]--; px[3]--; + generate_bezier(px, py, steps, + *(globals.blend_outer_cachex1[LEFT] + i), + *(globals.blend_outer_cachey1[LEFT] + i)); + } + /* inner left bump */ + py[0] += blend_lines; py[1] += blend_lines; py[2] += blend_lines; + px[3] += blend_lines; + for (i = 0; i < blend_lines; i++) + { + py[0]++; py[1]++; py[2]++; px[3]++; + generate_bezier(px, py, steps, + *(globals.blend_inner_cachex1[LEFT] + i), + *(globals.blend_inner_cachey1[LEFT] + i)); + } + + /* bottom half of bump */ + px[0] = x_offset - XFACTOR5 * tile_width; + px[1] = x_offset - XFACTOR6 * tile_width; + px[2] = x_offset - XFACTOR7 * tile_width; + px[3] = x_offset; + py[0] = curve_start_offset + YFACTOR5 * tile_height; + py[1] = curve_start_offset + YFACTOR6 * tile_height; + py[2] = curve_start_offset + YFACTOR7 * tile_height; + py[3] = curve_end_offset; + generate_bezier(px, py, steps, globals.cachex2[LEFT], + globals.cachey2[LEFT]); + /* outer left bump */ + for (i = 0; i < blend_lines; i++) + { + py[1]++; py[2]++; py[3]++; px[0]--; + generate_bezier(px, py, steps, + *(globals.blend_outer_cachex2[LEFT] + i), + *(globals.blend_outer_cachey2[LEFT] + i)); + } + /* inner left bump */ + py[1] -= blend_lines; py[2] -= blend_lines; py[3] -= blend_lines; + px[0] += blend_lines; + for (i = 0; i < blend_lines; i++) + { + py[1]--; py[2]--; py[3]--; px[0]++; + generate_bezier(px, py, steps, + *(globals.blend_inner_cachex2[LEFT] + i), + *(globals.blend_inner_cachey2[LEFT] + i)); + } + return; +} /* init_left_bump */ + +/*****/ + +static void +init_up_bump(gint width, gint height) +{ + gint i; + gint xtiles = config.x; + gint ytiles = config.y; + gint steps = globals.steps[UP]; + gint px[4], py[4]; + gint y_offset = 0; + gint tile_width = width / xtiles; + gint tile_height = height/ ytiles; + gint tile_width_eighth = tile_width / 8; + gint curve_start_offset = 0; + gint curve_end_offset = curve_start_offset + 2 * tile_width_eighth; + gint blend_lines = config.blend_lines; + + px[0] = curve_start_offset; + px[1] = curve_start_offset + YFACTOR2 * tile_width; + px[2] = curve_start_offset - YFACTOR3 * tile_width; + px[3] = curve_start_offset + YFACTOR4 * tile_width; + py[0] = y_offset; + py[1] = y_offset - XFACTOR2 * tile_height; + py[2] = y_offset - XFACTOR3 * tile_height; + py[3] = y_offset - XFACTOR4 * tile_height; + generate_bezier(px, py, steps, globals.cachex1[UP], + globals.cachey1[UP]); + /* outer up bump */ + for (i = 0; i < blend_lines; i++) + { + px[0]--; px[1]--; px[2]--; py[3]--; + generate_bezier(px, py, steps, + *(globals.blend_outer_cachex1[UP] + i), + *(globals.blend_outer_cachey1[UP] + i)); + } + /* inner up bump */ + px[0] += blend_lines; px[1] += blend_lines; px[2] += blend_lines; + py[3] += blend_lines; + for (i = 0; i < blend_lines; i++) + { + px[0]++; px[1]++; px[2]++; py[3]++; + generate_bezier(px, py, steps, + *(globals.blend_inner_cachex1[UP] + i), + *(globals.blend_inner_cachey1[UP] + i)); + } + + /* bottom half of bump */ + px[0] = curve_start_offset + YFACTOR5 * tile_width; + px[1] = curve_start_offset + YFACTOR6 * tile_width; + px[2] = curve_start_offset + YFACTOR7 * tile_width; + px[3] = curve_end_offset; + py[0] = y_offset - XFACTOR5 * tile_height; + py[1] = y_offset - XFACTOR6 * tile_height; + py[2] = y_offset - XFACTOR7 * tile_height; + py[3] = y_offset; + generate_bezier(px, py, steps, globals.cachex2[UP], + globals.cachey2[UP]); + /* outer up bump */ + for (i = 0; i < blend_lines; i++) + { + px[1]++; px[2]++; px[3]++; py[0]--; + generate_bezier(px, py, steps, + *(globals.blend_outer_cachex2[UP] + i), + *(globals.blend_outer_cachey2[UP] + i)); + } + /* inner up bump */ + px[1] -= blend_lines; px[2] -= blend_lines; px[3] -= blend_lines; + py[0] += blend_lines; + for (i = 0; i < blend_lines; i++) + { + px[1]--; px[2]--; px[3]--; py[0]++; + generate_bezier(px, py, steps, + *(globals.blend_inner_cachex2[UP] + i), + *(globals.blend_inner_cachey2[UP] + i)); + } + return; +} /* init_top_bump */ + +/*****/ + +static void +init_down_bump(gint width, gint height) +{ + gint i; + gint xtiles = config.x; + gint ytiles = config.y; + gint steps = globals.steps[DOWN]; + gint px[4], py[4]; + gint y_offset = 0; + gint tile_width = width / xtiles; + gint tile_height = height/ ytiles; + gint tile_width_eighth = tile_width / 8; + gint curve_start_offset = 0; + gint curve_end_offset = curve_start_offset + 2 * tile_width_eighth; + gint blend_lines = config.blend_lines; + + px[0] = curve_start_offset; + px[1] = curve_start_offset + YFACTOR2 * tile_width; + px[2] = curve_start_offset - YFACTOR3 * tile_width; + px[3] = curve_start_offset + YFACTOR4 * tile_width; + py[0] = y_offset; + py[1] = y_offset + XFACTOR2 * tile_height; + py[2] = y_offset + XFACTOR3 * tile_height; + py[3] = y_offset + XFACTOR4 * tile_height; + generate_bezier(px, py, steps, globals.cachex1[DOWN], + globals.cachey1[DOWN]); + /* outer down bump */ + for (i = 0; i < blend_lines; i++) + { + px[0]--; px[1]--; px[2]--; py[3]++; + generate_bezier(px, py, steps, + *(globals.blend_outer_cachex1[DOWN] + i), + *(globals.blend_outer_cachey1[DOWN] + i)); + } + /* inner down bump */ + px[0] += blend_lines; px[1] += blend_lines; px[2] += blend_lines; + py[3] -= blend_lines; + for (i = 0; i < blend_lines; i++) + { + px[0]++; px[1]++; px[2]++; py[3]--; + generate_bezier(px, py, steps, + *(globals.blend_inner_cachex1[DOWN] + i), + *(globals.blend_inner_cachey1[DOWN] + i)); + } + + /* bottom half of bump */ + px[0] = curve_start_offset + YFACTOR5 * tile_width; + px[1] = curve_start_offset + YFACTOR6 * tile_width; + px[2] = curve_start_offset + YFACTOR7 * tile_width; + px[3] = curve_end_offset; + py[0] = y_offset + XFACTOR5 * tile_height; + py[1] = y_offset + XFACTOR6 * tile_height; + py[2] = y_offset + XFACTOR7 * tile_height; + py[3] = y_offset; + generate_bezier(px, py, steps, globals.cachex2[DOWN], + globals.cachey2[DOWN]); + /* outer down bump */ + for (i = 0; i < blend_lines; i++) + { + px[1]++; px[2]++; px[3]++; py[0]++; + generate_bezier(px, py, steps, + *(globals.blend_outer_cachex2[DOWN] + i), + *(globals.blend_outer_cachey2[DOWN] + i)); + } + /* inner down bump */ + px[1] -= blend_lines; px[2] -= blend_lines; px[3] -= blend_lines; + py[0] -= blend_lines; + for (i = 0; i < blend_lines; i++) + { + px[1]--; px[2]--; px[3]--; py[0]--; + generate_bezier(px, py, steps, + *(globals.blend_inner_cachex2[DOWN] + i), + *(globals.blend_inner_cachey2[DOWN] + i)); + } + return; +} /* init_down_bump */ + +/*****/ + +static void +generate_grid(gint width, gint height, gint xtiles, gint ytiles, + gint *x, gint *y) +{ + gint i; + gint xlines = xtiles - 1; + gint ylines = ytiles - 1; + gint tile_width = width / xtiles; + gint tile_height = height / ytiles; + gint tile_width_leftover = width % xtiles; + gint tile_height_leftover = height % ytiles; + gint x_offset = tile_width; + gint y_offset = tile_height; + gint carry; + + for (i = 0; i < xlines; i++) + { + x[i] = x_offset; + x_offset += tile_width; + } + carry = 0; + while (tile_width_leftover) + { + for (i = carry; i < xlines; i++) + { + x[i] += 1; + } + tile_width_leftover--; + carry++; + } + x[xlines] = width - 1; /* padding for draw_horizontal_border */ + + for (i = 0; i < ytiles; i++) + { + y[i] = y_offset; + y_offset += tile_height; + } + carry = 0; + while (tile_height_leftover) + { + for (i = carry; i < ylines; i++) + { + y[i] += 1; + } + tile_height_leftover--; + carry++; + } + y[ylines] = height - 1; /* padding for draw_vertical_border */ + return; +} /* generate_grid */ + +/*****/ + +/* assumes RGB* */ +/* assumes py[1] > py[0] and px[0] = px[1] */ +static void +darken_vertical_line(guchar *buffer, gint width, gint bytes, + gint px[2], gint py[2], gdouble delta ) +{ + gint i; + gint rowstride = width * bytes; + gint index = px[0] * bytes + py[0] * rowstride; + gint length = py[1] - py[0] + 1; + gint temp; + + for (i = 0; i < length; i++) + { + DARKEN_POINT(buffer, index, delta, temp); + index += rowstride; + } + return; +} /* darken_vertical_line */ + +/*****/ + +/* assumes RGB* */ +/* assumes py[1] > py[0] and px[0] = px[1] */ +static void +lighten_vertical_line(guchar *buffer, gint width, gint bytes, + gint px[2], gint py[2], gdouble delta) +{ + gint i; + gint rowstride = width * bytes; + gint index = px[0] * bytes + py[0] * rowstride; + gint length = py[1] - py[0] + 1; + gint temp; + + for (i = 0; i < length; i++) + { + LIGHTEN_POINT(buffer, index, delta, temp); + index += rowstride; + } + return; +} /* lighten_vertical_line */ + +/*****/ + +/* assumes RGB* */ +/* assumes px[1] > px[0] and py[0] = py[1] */ +static void +darken_horizontal_line(guchar *buffer, gint width, gint bytes, + gint px[2], gint py[2], gdouble delta) +{ + gint i; + gint rowstride = width * bytes; + gint index = px[0] * bytes + py[0] * rowstride; + gint length = px[1] - px[0] + 1; + gint temp; + + for (i = 0; i < length; i++) + { + DARKEN_POINT(buffer, index, delta, temp); + index += bytes; + } + return; +} /* darken_horizontal_line */ + +/*****/ + +/* assumes RGB* */ +/* assumes px[1] > px[0] and py[0] = py[1] */ +static void +lighten_horizontal_line(guchar *buffer, gint width, gint bytes, + gint px[2], gint py[2], gdouble delta) +{ + gint i; + gint rowstride = width * bytes; + gint index = px[0] * bytes + py[0] * rowstride; + gint length = px[1] - px[0] + 1; + gint temp; + + for (i = 0; i < length; i++) + { + LIGHTEN_POINT(buffer, index, delta, temp); + index += bytes; + } + return; +} /* lighten_horizontal_line */ + +/*****/ + +static void +darken_right_bump(guchar *buffer, gint width, gint bytes, gint x_offset, + gint curve_start_offset, gint steps, gdouble delta, + gint counter) +{ + gint i; + gint x, y; + gint index; + gint last_index1 = -1; + gint last_index2 = -1; + gint rowstride = width * bytes; + gint temp; + gint j = counter; + + for (i = 0; i < steps; i++) + { + x = x_offset + + *(*(globals.blend_inner_cachex1[RIGHT] + j) + i); + y = curve_start_offset + + *(*(globals.blend_inner_cachey1[RIGHT] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index1) + { + if (i < steps / 1.3) + { + LIGHTEN_POINT(buffer, index, delta, temp); + } + else + { + DARKEN_POINT(buffer, index, delta, temp); + } + last_index1 = index; + } + + x = x_offset + + *(*(globals.blend_inner_cachex2[RIGHT] + j) + i); + y = curve_start_offset + + *(*(globals.blend_inner_cachey2[RIGHT] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index2) + { + DARKEN_POINT(buffer, index, delta, temp); + last_index2 = index; + } + } + return; +} /* darken_right_bump */ + +/*****/ + +static void +lighten_right_bump(guchar *buffer, gint width, gint bytes, gint x_offset, + gint curve_start_offset, gint steps, gdouble delta, + gint counter) +{ + gint i; + gint x, y; + gint index; + gint last_index1 = -1; + gint last_index2 = -1; + gint rowstride = width * bytes; + gint temp; + gint j = counter; + + for (i = 0; i < steps; i++) + { + x = x_offset + + *(*(globals.blend_outer_cachex1[RIGHT] + j) + i); + y = curve_start_offset + + *(*(globals.blend_outer_cachey1[RIGHT] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index1) + { + if (i < steps / 1.3) + { + DARKEN_POINT(buffer, index, delta, temp); + } + else + { + LIGHTEN_POINT(buffer, index, delta, temp); + } + last_index1 = index; + } + + x = x_offset + + *(*(globals.blend_outer_cachex2[RIGHT] + j) + i); + y = curve_start_offset + + *(*(globals.blend_outer_cachey2[RIGHT] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index2) + { + LIGHTEN_POINT(buffer, index, delta, temp); + last_index2 = index; + } + } + return; +} /* lighten_right_bump */ + +/*****/ + +static void +darken_left_bump(guchar *buffer, gint width, gint bytes, gint x_offset, + gint curve_start_offset, gint steps, gdouble delta, + gint counter) +{ + gint i; + gint x, y; + gint index; + gint last_index1 = -1; + gint last_index2 = -1; + gint rowstride = width * bytes; + gint temp; + gint j = counter; + + for (i = 0; i < steps; i++) + { + x = x_offset + + *(*(globals.blend_outer_cachex1[LEFT] + j) + i); + y = curve_start_offset + + *(*(globals.blend_outer_cachey1[LEFT] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index1) + { + DARKEN_POINT(buffer, index, delta, temp); + last_index1 = index; + } + + x = x_offset + + *(*(globals.blend_outer_cachex2[LEFT] + j) + i); + y = curve_start_offset + + *(*(globals.blend_outer_cachey2[LEFT] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index2) + { + if (i < steps / 4) + { + DARKEN_POINT(buffer, index, delta, temp); + } + else + { + LIGHTEN_POINT(buffer, index, delta, temp); + } + last_index2 = index; + } + } + return; +} /* darken_left_bump */ + +/*****/ + +static void +lighten_left_bump(guchar *buffer, gint width, gint bytes, gint x_offset, + gint curve_start_offset, gint steps, gdouble delta, + gint counter) +{ + gint i; + gint x, y; + gint index; + gint last_index1 = -1; + gint last_index2 = -1; + gint rowstride = width * bytes; + gint temp; + gint j = counter; + + for (i = 0; i < steps; i++) + { + x = x_offset + + *(*(globals.blend_inner_cachex1[LEFT] + j) + i); + y = curve_start_offset + + *(*(globals.blend_inner_cachey1[LEFT] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index1) + { + LIGHTEN_POINT(buffer, index, delta, temp); + last_index1 = index; + } + + x = x_offset + + *(*(globals.blend_inner_cachex2[LEFT] + j) + i); + y = curve_start_offset + + *(*(globals.blend_inner_cachey2[LEFT] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index2) + { + if (i < steps / 4) + { + LIGHTEN_POINT(buffer, index, delta, temp); + } + else + { + DARKEN_POINT(buffer, index, delta, temp); + } + last_index2 = index; + } + } + return; +} /* lighten_left_bump */ + +/*****/ + +static void +darken_up_bump(guchar *buffer, gint width, gint bytes, gint y_offset, + gint curve_start_offset, gint steps, gdouble delta, + gint counter) +{ + gint i; + gint x, y; + gint index; + gint last_index1 = -1; + gint last_index2 = -1; + gint rowstride = width * bytes; + gint temp; + gint j = counter; + + for (i = 0; i < steps; i++) + { + x = curve_start_offset + + *(*(globals.blend_outer_cachex1[UP] + j) + i); + y = y_offset + + *(*(globals.blend_outer_cachey1[UP] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index1) + { + DARKEN_POINT(buffer, index, delta, temp); + last_index1 = index; + } + + x = curve_start_offset + + *(*(globals.blend_outer_cachex2[UP] + j) + i); + y = y_offset + + *(*(globals.blend_outer_cachey2[UP] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index2) + { + if (i < steps / 4) + { + DARKEN_POINT(buffer, index, delta, temp); + } + else + { + LIGHTEN_POINT(buffer, index, delta, temp); + } + last_index2 = index; + } + } + return; +} /* darken_up_bump */ + +/*****/ + +static void +lighten_up_bump(guchar *buffer, gint width, gint bytes, gint y_offset, + gint curve_start_offset, gint steps, gdouble delta, + gint counter) +{ + gint i; + gint x, y; + gint index; + gint last_index1 = -1; + gint last_index2 = -1; + gint rowstride = width * bytes; + gint temp; + gint j = counter; + + for (i = 0; i < steps; i++) + { + x = curve_start_offset + + *(*(globals.blend_inner_cachex1[UP] + j) + i); + y = y_offset + + *(*(globals.blend_inner_cachey1[UP] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index1) + { + LIGHTEN_POINT(buffer, index, delta, temp); + last_index1 = index; + } + + x = curve_start_offset + + *(*(globals.blend_inner_cachex2[UP] + j) + i); + y = y_offset + + *(*(globals.blend_inner_cachey2[UP] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index2) + { + if (i < steps / 4) + { + LIGHTEN_POINT(buffer, index, delta, temp); + } + else + { + DARKEN_POINT(buffer, index, delta, temp); + } + last_index2 = index; + } + } + return; +} /* lighten_up_bump */ + +/*****/ + +static void +darken_down_bump(guchar *buffer, gint width, gint bytes, gint y_offset, + gint curve_start_offset, gint steps, gdouble delta, + gint counter) +{ + gint i; + gint x, y; + gint index; + gint last_index1 = -1; + gint last_index2 = -1; + gint rowstride = width * bytes; + gint temp; + gint j = counter; + + for (i = 0; i < steps; i++) + { + x = curve_start_offset + + *(*(globals.blend_inner_cachex1[DOWN] + j) + i); + y = y_offset + + *(*(globals.blend_inner_cachey1[DOWN] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index1) + { + if (i < steps / 1.2) + { + LIGHTEN_POINT(buffer, index, delta, temp); + } + else + { + DARKEN_POINT(buffer, index, delta, temp); + } + last_index1 = index; + } + + x = curve_start_offset + + *(*(globals.blend_inner_cachex2[DOWN] + j) + i); + y = y_offset + + *(*(globals.blend_inner_cachey2[DOWN] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index2) + { + DARKEN_POINT(buffer, index, delta, temp); + last_index2 = index; + } + } + return; +} /* darken_down_bump */ + +/*****/ + +static void +lighten_down_bump(guchar *buffer, gint width, gint bytes, gint y_offset, + gint curve_start_offset, gint steps, gdouble delta, + gint counter) +{ + gint i; + gint x, y; + gint index; + gint last_index1 = -1; + gint last_index2 = -1; + gint rowstride = width * bytes; + gint temp; + gint j = counter; + + for (i = 0; i < steps; i++) + { + x = curve_start_offset + + *(*(globals.blend_outer_cachex1[DOWN] + j) + i); + y = y_offset + + *(*(globals.blend_outer_cachey1[DOWN] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index1) + { + if (i < steps / 1.2) + { + DARKEN_POINT(buffer, index, delta, temp); + } + else + { + LIGHTEN_POINT(buffer, index, delta, temp); + } + last_index1 = index; + } + + x = curve_start_offset + + *(*(globals.blend_outer_cachex2[DOWN] + j) + i); + y = y_offset + + *(*(globals.blend_outer_cachey2[DOWN] + j) + i); + index = y * rowstride + x * bytes; + if (index != last_index2) + { + LIGHTEN_POINT(buffer, index, delta, temp); + last_index2 = index; + } + } + return; +} /* lighten_down_bump */ + +/*****/ + +static void +draw_bezier_line(guchar *buffer, gint width, gint bytes, + gint steps, gint *cx, gint *cy) +{ + gint i; + gint x, y; + gint index; + gint rowstride = width * bytes; + + for (i = 0; i < steps; i++) + { + x = cx[i]; + y = cy[i]; + index = y * rowstride + x * bytes; + DRAW_POINT(buffer, index); + } + return; +} /* draw_bezier_line */ + +/*****/ + +static void +darken_bezier_line(guchar *buffer, gint width, gint bytes, + gint x_offset, gint y_offset, gint steps, + gint *cx, gint *cy, gdouble delta) +{ + gint i; + gint x, y; + gint index; + gint last_index = -1; + gint rowstride = width * bytes; + gint temp; + + for (i = 0; i < steps; i++) + { + x = cx[i] + x_offset; + y = cy[i] + y_offset; + index = y * rowstride + x * bytes; + if (index != last_index) + { + DARKEN_POINT(buffer, index, delta, temp); + last_index = index; + } + } + return; +} /* darken_bezier_line */ + +/*****/ + +static void +lighten_bezier_line(guchar *buffer, gint width, gint bytes, + gint x_offset, gint y_offset, gint steps, + gint *cx, gint *cy, gdouble delta) +{ + gint i; + gint x, y; + gint index; + gint last_index = -1; + gint rowstride = width * bytes; + gint temp; + + for (i = 0; i < steps; i++) + { + x = cx[i] + x_offset; + y = cy[i] + y_offset; + index = y * rowstride + x * bytes; + if (index != last_index) + { + LIGHTEN_POINT(buffer, index, delta, temp); + last_index = index; + } + } + return; +} /* lighten_bezier_line */ + +/*****/ + +static void +draw_bezier_vertical_border(guchar *buffer, gint width, gint height, + gint bytes, gint x_offset, gint xtiles, + gint ytiles, gint blend_lines, + gdouble blend_amount, gint steps) +{ + gint i, j; + gint tile_width = width / xtiles; + gint tile_height = height / ytiles; + gint tile_height_eighth = tile_height / 8; + gint curve_start_offset = 3 * tile_height_eighth; + gint curve_end_offset = curve_start_offset + 2 * tile_height_eighth; + gint px[4], py[4]; + gint y_offset = 0; + gdouble delta; + gdouble sigma = blend_amount / blend_lines; + gint right; + bump_t style_index; + gint *cachex, *cachey; + + cachex = g_malloc(steps * sizeof(gint)); + cachey = g_malloc(steps * sizeof(gint)); + + for (i = 0; i < ytiles; i++) + { + right = rand() & 1; + if (right) + { + style_index = RIGHT; + } + else + { + style_index = LEFT; + } + px[0] = px[3] = x_offset; + px[1] = x_offset + WALL_XFACTOR2 * tile_width * FUDGE; + px[2] = x_offset + WALL_XFACTOR3 * tile_width * FUDGE; + py[0] = y_offset; + py[1] = y_offset + WALL_YCONS2 * tile_height; + py[2] = y_offset + WALL_YCONS3 * tile_height; + py[3] = y_offset + curve_start_offset; + + if (right) + { + px[1] = x_offset - WALL_XFACTOR2 * tile_width; + px[2] = x_offset - WALL_XFACTOR3 * tile_width; + } + generate_bezier(px, py, steps, cachex, cachey); + draw_bezier_line(buffer, width, bytes, steps, cachex, cachey); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + px[0] = -j - 1; + darken_bezier_line(buffer, width, bytes, px[0], 0, + steps, cachex, cachey, delta); + px[0] = j + 1; + lighten_bezier_line(buffer, width, bytes, px[0], 0, + steps, cachex, cachey, delta); + delta -= sigma; + } + if (right) + { + draw_right_bump(buffer, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[RIGHT]); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + /* use to be -j -1 */ + darken_right_bump(buffer, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[RIGHT], delta, j); + /* use to be +j + 1 */ + lighten_right_bump(buffer, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[RIGHT], delta, j); + delta -= sigma; + } + } + else + { + draw_left_bump(buffer, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[LEFT]); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + /* use to be -j -1 */ + darken_left_bump(buffer, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[LEFT], delta, j); + /* use to be -j - 1 */ + lighten_left_bump(buffer, width, bytes, x_offset, + y_offset + curve_start_offset, + globals.steps[LEFT], delta, j); + delta -= sigma; + } + } + px[0] = px[3] = x_offset; + px[1] = x_offset + WALL_XFACTOR2 * tile_width * FUDGE; + px[2] = x_offset + WALL_XFACTOR3 * tile_width * FUDGE; + py[0] = y_offset + curve_end_offset; + py[1] = y_offset + curve_end_offset + WALL_YCONS2 * tile_height; + py[2] = y_offset + curve_end_offset + WALL_YCONS3 * tile_height; + py[3] = globals.gridy[i]; + if (right) + { + px[1] = x_offset - WALL_XFACTOR2 * tile_width; + px[2] = x_offset - WALL_XFACTOR3 * tile_width; + } + generate_bezier(px, py, steps, cachex, cachey); + draw_bezier_line(buffer, width, bytes, steps, cachex, cachey); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + px[0] = -j - 1; + darken_bezier_line(buffer, width, bytes, px[0], 0, + steps, cachex, cachey, delta); + px[0] = j + 1; + lighten_bezier_line(buffer, width, bytes, px[0], 0, + steps, cachex, cachey, delta); + delta -= sigma; + } + y_offset = globals.gridy[i]; + } /* for */ + g_free(cachex); + g_free(cachey); + + return; +} /* draw_bezier_vertical_border */ + +/*****/ + +static void +draw_bezier_horizontal_border(guchar *buffer, gint width, gint height, + gint bytes, gint y_offset, gint xtiles, + gint ytiles, gint blend_lines, + gdouble blend_amount, gint steps) +{ + gint i, j; + gint tile_width = width / xtiles; + gint tile_height = height / ytiles; + gint tile_width_eighth = tile_width / 8; + gint curve_start_offset = 3 * tile_width_eighth; + gint curve_end_offset = curve_start_offset + 2 * tile_width_eighth; + gint px[4], py[4]; + gint x_offset = 0; + gdouble delta; + gdouble sigma = blend_amount / blend_lines; + gint up; + style_t style_index; + gint *cachex, *cachey; + + cachex = g_malloc(steps * sizeof(gint)); + cachey = g_malloc(steps * sizeof(gint)); + + for (i = 0; i < xtiles; i++) + { + up = rand() & 1; + if (up) + { + style_index = UP; + } + else + { + style_index = DOWN; + } + px[0] = x_offset; + px[1] = x_offset + WALL_XCONS2 * tile_width; + px[2] = x_offset + WALL_XCONS3 * tile_width; + px[3] = x_offset + curve_start_offset; + py[0] = py[3] = y_offset; + py[1] = y_offset + WALL_YFACTOR2 * tile_height * FUDGE; + py[2] = y_offset + WALL_YFACTOR3 * tile_height * FUDGE; + if (!up) + { + py[1] = y_offset - WALL_YFACTOR2 * tile_height; + py[2] = y_offset - WALL_YFACTOR3 * tile_height; + } + generate_bezier(px, py, steps, cachex, cachey); + draw_bezier_line(buffer, width, bytes, steps, cachex, cachey); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + py[0] = -j - 1; + darken_bezier_line(buffer, width, bytes, 0, py[0], + steps, cachex, cachey, delta); + py[0] = j + 1; + lighten_bezier_line(buffer, width, bytes, 0, py[0], + steps, cachex, cachey, delta); + delta -= sigma; + } + /* bumps */ + if (up) + { + draw_up_bump(buffer, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[UP]); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + /* use to be -j -1 */ + darken_up_bump(buffer, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[UP], delta, j); + /* use to be +j + 1 */ + lighten_up_bump(buffer, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[UP], delta, j); + delta -= sigma; + } + } + else + { + draw_down_bump(buffer, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[DOWN]); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + /* use to be +j + 1 */ + darken_down_bump(buffer, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[DOWN], delta, j); + /* use to be -j -1 */ + lighten_down_bump(buffer, width, bytes, y_offset, + x_offset + curve_start_offset, + globals.steps[DOWN], delta, j); + delta -= sigma; + } + } + /* ending side wall line */ + px[0] = x_offset + curve_end_offset; + px[1] = x_offset + curve_end_offset + WALL_XCONS2 * tile_width; + px[2] = x_offset + curve_end_offset + WALL_XCONS3 * tile_width; + px[3] = globals.gridx[i]; + py[0] = py[3] = y_offset; + py[1] = y_offset + WALL_YFACTOR2 * tile_height * FUDGE; + py[2] = y_offset + WALL_YFACTOR3 * tile_height * FUDGE; + if (!up) + { + py[1] = y_offset - WALL_YFACTOR2 * tile_height; + py[2] = y_offset - WALL_YFACTOR3 * tile_height; + } + generate_bezier(px, py, steps, cachex, cachey); + draw_bezier_line(buffer, width, bytes, steps, cachex, cachey); + delta = blend_amount; + for (j = 0; j < blend_lines; j++) + { + py[0] = -j - 1; + darken_bezier_line(buffer, width, bytes, 0, py[0], + steps, cachex, cachey, delta); + py[0] = j + 1; + lighten_bezier_line(buffer, width, bytes, 0, py[0], + steps, cachex, cachey, delta); + delta -= sigma; + } + x_offset = globals.gridx[i]; + } /* for */ + g_free(cachex); + g_free(cachey); + + return; +} /* draw_bezier_horizontal_border */ + +/*****/ + +static void +check_config(gint width, gint height) +{ + gint tile_width, tile_height; + gint tile_width_limit, tile_height_limit; + + if (config.x < 1) + { + config.x = 1; + } + if (config.y < 1) + { + config.y = 1; + } + if (config.blend_amount < 0) + { + config.blend_amount = 0; + } + if (config.blend_amount > 5) + { + config.blend_amount = 5; + } + tile_width = width / config.x; + tile_height = height / config.y; + tile_width_limit = 0.4 * tile_width; + tile_height_limit = 0.4 * tile_height; + if ((config.blend_lines > tile_width_limit) + || (config.blend_lines > tile_height_limit)) + { + config.blend_lines = MIN(tile_width_limit, tile_height_limit); + } + return; +} /*check_config */ + +/*****/ + +/******************************************************** + GUI +********************************************************/ + +static void +dialog_box(void) +{ + GtkWidget *dlg; + GtkWidget *button; + GtkWidget *rbutton; + GtkWidget *cbutton; + GSList *list; + GtkWidget *hbox; + GtkWidget *frame; + GtkWidget *label; + GtkWidget *entry; + GtkWidget *table; + GtkWidget *scale; + GtkObject *adjustment; + GtkTooltips *tooltips; + GdkColor tips_fg, tips_bg; + + gchar buffer[12]; + + gchar **argv; + gint argc; + + argc = 1; + argv = g_new(gchar *, 1); + argv[0] = g_strdup(PLUG_IN_NAME); + + gtk_init(&argc, &argv); + gtk_rc_parse(gimp_gtkrc()); + + /* Create the dialog box */ + + dlg = gtk_dialog_new(); + gtk_window_set_title(GTK_WINDOW(dlg), PLUG_IN_NAME); + gtk_window_position(GTK_WINDOW(dlg), GTK_WIN_POS_MOUSE); + gtk_signal_connect(GTK_OBJECT(dlg), "destroy", + (GtkSignalFunc) dialog_close_callback, NULL); + + /* Action Area */ + + button = gtk_button_new_with_label("OK"); + GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); + gtk_signal_connect(GTK_OBJECT(button), "clicked", + (GtkSignalFunc) run_callback, dlg); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->action_area), button, + TRUE, TRUE, 0); + gtk_widget_grab_default(button); + gtk_widget_show(button); + + button = gtk_button_new_with_label("Cancel"); + GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); + gtk_signal_connect_object(GTK_OBJECT(button), "clicked", + (GtkSignalFunc) gtk_widget_destroy, + GTK_OBJECT(dlg)); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->action_area), button, + TRUE, TRUE, 0); + gtk_widget_show(button); + + /* init tooltips */ + tooltips = gtk_tooltips_new(); + tips_fg.red = 0; + tips_fg.green = 0; + tips_fg.blue = 0; + gdk_color_alloc(gtk_widget_get_colormap(dlg), &tips_fg); + tips_bg.red = 61669; + tips_bg.green = 59113; + tips_bg.blue = 35979; + gdk_color_alloc(gtk_widget_get_colormap(dlg), &tips_bg); + gtk_tooltips_set_colors(tooltips, &tips_bg, &tips_fg); + if (globals.tooltips == 0) + { + gtk_tooltips_disable(tooltips); + } + /* paramters frame */ + frame = gtk_frame_new("Number of Tiles"); + gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN); + gtk_container_border_width(GTK_CONTAINER(frame), 10); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->vbox), frame, TRUE, TRUE, 0); + + table = gtk_table_new(3, 2, FALSE); + gtk_container_border_width (GTK_CONTAINER(table), 10); + gtk_container_add(GTK_CONTAINER(frame), table); + + /* xtiles */ + label = gtk_label_new("Horizontal:"); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 5, 0); + gtk_widget_show(label); + + adjustment = gtk_adjustment_new(config.x, MIN_XTILES, MAX_XTILES, + 1.0, 1.0, 0); + gtk_signal_connect(GTK_OBJECT(adjustment), "value_changed", + (GtkSignalFunc) adjustment_callback, + &config.x); + scale = gtk_hscale_new(GTK_ADJUSTMENT(adjustment)); + gtk_widget_set_usize(scale, SCALE_WIDTH, 0); + gtk_table_attach(GTK_TABLE(table), scale, 1, 2, 0, 1, GTK_FILL, 0, 0, 0); + gtk_scale_set_draw_value(GTK_SCALE(scale), FALSE); + gtk_range_set_update_policy(GTK_RANGE(scale), GTK_UPDATE_CONTINUOUS); + gtk_widget_show(scale); + gtk_tooltips_set_tip(tooltips, scale, XTILES_TEXT, NULL); + + entry = gtk_entry_new(); + gtk_object_set_user_data(GTK_OBJECT(entry), adjustment); + gtk_object_set_user_data(GTK_OBJECT(adjustment), entry); + gtk_widget_set_usize(entry, ENTRY_WIDTH, 0); + sprintf(buffer, "%i", config.x); + gtk_entry_set_text(GTK_ENTRY(entry), buffer); + gtk_signal_connect(GTK_OBJECT(entry), "changed", + GTK_SIGNAL_FUNC(entry_callback), + (gpointer) &config.x); + gtk_table_attach(GTK_TABLE(table), entry, 2, 3, 0, 1, GTK_FILL, 0, 0, 0); + gtk_widget_show(entry); + gtk_tooltips_set_tip(tooltips, entry, XTILES_TEXT, NULL); + + /* ytiles */ + label = gtk_label_new("Vertical:"); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, GTK_FILL, 0, 5, 0); + gtk_widget_show(label); + + adjustment = gtk_adjustment_new(config.y, MIN_YTILES, MAX_YTILES, + 1.0, 1.0, 0 ); + gtk_signal_connect(GTK_OBJECT(adjustment), "value_changed", + (GtkSignalFunc) adjustment_callback, + &config.y); + scale = gtk_hscale_new(GTK_ADJUSTMENT(adjustment)); + gtk_widget_set_usize(scale, SCALE_WIDTH, 0); + gtk_table_attach(GTK_TABLE(table), scale, 1, 2, 1, 2, GTK_FILL, 0, 0, 0); + gtk_scale_set_draw_value(GTK_SCALE(scale), FALSE); + gtk_range_set_update_policy(GTK_RANGE(scale), GTK_UPDATE_CONTINUOUS); + gtk_widget_show(scale); + gtk_tooltips_set_tip(tooltips, scale, YTILES_TEXT, NULL); + + entry = gtk_entry_new(); + gtk_object_set_user_data(GTK_OBJECT(entry), adjustment); + gtk_object_set_user_data(GTK_OBJECT(adjustment), entry); + gtk_widget_set_usize(entry, ENTRY_WIDTH, 0); + sprintf(buffer, "%i", config.y); + gtk_entry_set_text(GTK_ENTRY(entry), buffer); + gtk_signal_connect(GTK_OBJECT(entry), "changed", + GTK_SIGNAL_FUNC(entry_callback), + (gpointer) &config.y); + gtk_table_attach(GTK_TABLE(table), entry, 2, 3, 1, 2, GTK_FILL, 0, 0, 0); + gtk_widget_show(entry); + gtk_tooltips_set_tip(tooltips, entry, YTILES_TEXT, NULL); + + gtk_widget_show(table); + gtk_widget_show(frame); + + /* frame for bevel blending */ + + frame = gtk_frame_new("Bevel Edges"); + gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN); + gtk_container_border_width(GTK_CONTAINER(frame), 10); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->vbox), frame, TRUE, TRUE, 0); + + table = gtk_table_new(3, 2, FALSE); + gtk_container_border_width(GTK_CONTAINER(table), 10); + gtk_container_add(GTK_CONTAINER(frame), table); + + /* number of blending lines */ + label = gtk_label_new("Bevel width:"); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 5, 0); + gtk_widget_show(label); + + adjustment = gtk_adjustment_new(config.blend_lines, MIN_BLEND_LINES, + MAX_BLEND_LINES, 1.0, 1.0, 0); + gtk_signal_connect(GTK_OBJECT(adjustment), "value_changed", + (GtkSignalFunc) adjustment_callback, + &config.blend_lines); + scale = gtk_hscale_new(GTK_ADJUSTMENT(adjustment)); + gtk_widget_set_usize(scale, SCALE_WIDTH, 0); + gtk_table_attach(GTK_TABLE(table), scale, 1, 2, 0, 1, GTK_FILL, 0, 0, 0); + gtk_scale_set_draw_value(GTK_SCALE(scale), FALSE); + gtk_range_set_update_policy(GTK_RANGE(scale), GTK_UPDATE_CONTINUOUS); + gtk_widget_show(scale); + gtk_tooltips_set_tip(tooltips, scale, BLEND_LINES_TEXT, NULL); + + entry = gtk_entry_new(); + gtk_object_set_user_data(GTK_OBJECT(entry), adjustment); + gtk_object_set_user_data(GTK_OBJECT(adjustment), entry); + gtk_widget_set_usize(entry, ENTRY_WIDTH, 0); + sprintf(buffer, "%i", config.blend_lines); + gtk_entry_set_text(GTK_ENTRY(entry), buffer); + gtk_signal_connect(GTK_OBJECT(entry), "changed", + GTK_SIGNAL_FUNC(entry_callback), + (gpointer) &config.blend_lines); + gtk_table_attach(GTK_TABLE(table), entry, 2, 3, 0, 1, GTK_FILL, 0, 0, 0); + gtk_widget_show(entry); + gtk_tooltips_set_tip(tooltips, entry, BLEND_LINES_TEXT, NULL); + + /* blending amount */ + label = gtk_label_new("Highlight:"); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, GTK_FILL, 0, 5, 0); + gtk_widget_show(label); + + adjustment = gtk_adjustment_new(config.blend_amount, MIN_BLEND_AMOUNT, + MAX_BLEND_AMOUNT, 1.0, 0.05, 0.0); + gtk_signal_connect(GTK_OBJECT(adjustment), "value_changed", + (GtkSignalFunc) adjustment_double_callback, + &config.blend_amount); + scale = gtk_hscale_new(GTK_ADJUSTMENT(adjustment)); + gtk_widget_set_usize(scale, SCALE_WIDTH, 0); + gtk_table_attach(GTK_TABLE(table), scale, 1, 2, 1, 2, GTK_FILL, 0, 0, 0); + gtk_scale_set_draw_value(GTK_SCALE(scale), FALSE); + gtk_range_set_update_policy(GTK_RANGE(scale), GTK_UPDATE_CONTINUOUS); + gtk_widget_show(scale); + gtk_tooltips_set_tip(tooltips, scale, BLEND_AMOUNT_TEXT, NULL); + + entry = gtk_entry_new(); + gtk_object_set_user_data(GTK_OBJECT(entry), adjustment); + gtk_object_set_user_data(GTK_OBJECT(adjustment), entry); + gtk_widget_set_usize(entry, ENTRY_WIDTH, 0); + sprintf(buffer, "%0.2f", config.blend_amount); + gtk_entry_set_text(GTK_ENTRY(entry), buffer); + gtk_signal_connect(GTK_OBJECT(entry), "changed", + GTK_SIGNAL_FUNC(entry_double_callback), + (gpointer) &config.blend_amount); + gtk_table_attach(GTK_TABLE(table), entry, 2, 3, 1, 2, GTK_FILL, 0, 0, 0); + gtk_widget_show(entry); + gtk_tooltips_set_tip(tooltips, entry, BLEND_AMOUNT_TEXT, NULL); + + gtk_widget_show(table); + gtk_widget_show(frame); + + /* frame for primitive radio buttons */ + + hbox = gtk_hbox_new(FALSE, 5); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->vbox), hbox, FALSE, FALSE, 0); + + frame = gtk_frame_new("Jigsaw Style"); + gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN); + gtk_container_border_width(GTK_CONTAINER(frame), 10); + gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 0); + + table = gtk_table_new(2, 1, FALSE); + gtk_container_border_width(GTK_CONTAINER(table), 5); + gtk_container_add(GTK_CONTAINER(frame), table); + + rbutton = gtk_radio_button_new_with_label(NULL, "Square"); + list = gtk_radio_button_group((GtkRadioButton *) rbutton); + gtk_toggle_button_set_state((GtkToggleButton *) rbutton, + config.style == BEZIER_1 ? TRUE : FALSE); + gtk_signal_connect(GTK_OBJECT(rbutton), "toggled", + GTK_SIGNAL_FUNC(radio_button_primitive_callback), + (gpointer) BEZIER_1); + gtk_table_attach(GTK_TABLE(table), rbutton, 0, 1, 0, 1, GTK_FILL, 0, 10, 0); + gtk_widget_show(rbutton); + gtk_tooltips_set_tip(tooltips, rbutton, SQUARE_TEXT, NULL); + + rbutton = gtk_radio_button_new_with_label(list, "Curved"); + list = gtk_radio_button_group((GtkRadioButton *) rbutton); + gtk_toggle_button_set_state((GtkToggleButton *) rbutton, + config.style == BEZIER_2 ? TRUE : FALSE); + gtk_signal_connect(GTK_OBJECT(rbutton), "toggled", + GTK_SIGNAL_FUNC(radio_button_primitive_callback), + (gpointer) BEZIER_2); + gtk_table_attach(GTK_TABLE(table), rbutton, 1, 2, 0, 1, GTK_FILL, 0, 10, 0); + gtk_widget_show(rbutton); + gtk_tooltips_set_tip(tooltips, rbutton, CURVE_TEXT, NULL); + + gtk_widget_show(table); + gtk_widget_show(frame); + + table = gtk_table_new(1, 3, FALSE); + gtk_container_border_width(GTK_CONTAINER(table), 3); + gtk_box_pack_start(GTK_BOX(hbox), table, TRUE, TRUE, 0); + + cbutton = gtk_check_button_new_with_label("Disable Tooltips"); + gtk_toggle_button_set_state((GtkToggleButton *) cbutton, + globals.tooltips ? FALSE : TRUE); + gtk_signal_connect(GTK_OBJECT(cbutton), "toggled", + (GtkSignalFunc) check_button_callback, + (gpointer) tooltips); + gtk_table_attach(GTK_TABLE(table), cbutton, 0, 1, 1, 2, 0, 0, 0, 20); + gtk_widget_show(cbutton); + gtk_tooltips_set_tip(tooltips, cbutton, DISABLE_TEXT, NULL); + + gtk_widget_show(table); + gtk_widget_show(hbox); + gtk_widget_show(dlg); + + gtk_main(); + gdk_flush(); + + return; +} + +/*************************************************** + callbacks + ***************************************************/ + +static void +run_callback(GtkWidget *widget, gpointer data) +{ + globals.dialog_result = 1; + gtk_widget_destroy(GTK_WIDGET(data)); + return; +} + +/*****/ + +static void +dialog_close_callback(GtkWidget *widget, gpointer data) +{ + gtk_main_quit(); + return; +} /* dialog_close_callback */ + +/*****/ + +static void +entry_callback(GtkWidget *widget, gpointer data) +{ + GtkAdjustment *adjustment; + gint new_value; + + new_value = atoi(gtk_entry_get_text(GTK_ENTRY(widget))); + + if (*(gint *)data != new_value) + { + *(gint *)data = new_value; + adjustment = gtk_object_get_user_data(GTK_OBJECT(widget)); + if ((new_value >= adjustment->lower) + && (new_value <= adjustment->upper)) + { + adjustment->value = new_value; + gtk_signal_handler_block_by_data(GTK_OBJECT(adjustment), data); + gtk_signal_emit_by_name(GTK_OBJECT(adjustment), "value_changed"); + gtk_signal_handler_unblock_by_data(GTK_OBJECT(adjustment), data); + } + } + return; +} /* entry_callback */ + +/*****/ + +static void +radio_button_primitive_callback(GtkWidget *widget, gpointer data) +{ + if (GTK_TOGGLE_BUTTON (widget)->active) /* button just got checked */ + { + if (data == (gpointer) BEZIER_1) + { + config.style = BEZIER_1; + } + else if (data == (gpointer) BEZIER_2) + { + config.style = BEZIER_2; + } + else + { + printf("radio_button_callback: bad data\n"); + } + } + return; +} /* radio_button_primitive_callback */ + +/*****/ + +static void +check_button_callback(GtkWidget *widget, gpointer data) +{ + if (GTK_TOGGLE_BUTTON (widget)->active) + { + gtk_tooltips_disable((GtkTooltips *) data); + globals.tooltips = 0; + } + else + { + gtk_tooltips_enable((GtkTooltips *) data); + globals.tooltips = 1; + } + return; +} /* check_button_callback */ + +/*****/ + +static void +adjustment_callback(GtkAdjustment *adjustment, gpointer data) +{ + GtkWidget *entry; + gchar buffer[50]; + + if (*(gint *)data != adjustment->value) + { + *(gint *)data = adjustment->value; + entry = gtk_object_get_user_data(GTK_OBJECT(adjustment)); + sprintf(buffer, "%d", *(gint *)data); + + gtk_signal_handler_block_by_data(GTK_OBJECT(entry), data); + gtk_entry_set_text(GTK_ENTRY(entry), buffer); + gtk_signal_handler_unblock_by_data(GTK_OBJECT(entry), data); + } + return; +} /* adjustment_callback */ + +/*****/ + +static void +adjustment_double_callback(GtkAdjustment *adjustment, gpointer data) +{ + GtkWidget *entry; + gchar buffer[50]; + + if (*(gdouble *)data != adjustment->value) + { + *(gdouble *)data = adjustment->value; + entry = gtk_object_get_user_data(GTK_OBJECT(adjustment)); + sprintf(buffer, "%0.2f", *(gdouble *)data); + + gtk_signal_handler_block_by_data(GTK_OBJECT(entry), data); + gtk_entry_set_text(GTK_ENTRY(entry), buffer); + gtk_signal_handler_unblock_by_data(GTK_OBJECT(entry), data); + } + return; +} /* adjustment_double_callback */ + +/*****/ + +static void +entry_double_callback(GtkWidget *widget, gpointer data) +{ + GtkAdjustment *adjustment; + gdouble new_value; + + new_value = atof(gtk_entry_get_text(GTK_ENTRY(widget))); + + if (*(gdouble *)data != new_value) + { + *(gdouble *)data = new_value; + adjustment = gtk_object_get_user_data(GTK_OBJECT(widget)); + if ((new_value >= adjustment->lower) + && (new_value <= adjustment->upper)) + { + adjustment->value = new_value; + gtk_signal_handler_block_by_data(GTK_OBJECT(adjustment), data); + gtk_signal_emit_by_name(GTK_OBJECT(adjustment), "value_changed"); + gtk_signal_handler_unblock_by_data(GTK_OBJECT(adjustment), data); + } + } + return; +} /* entry_double_callback */ diff --git a/plug-ins/newsprint/.cvsignore b/plug-ins/newsprint/.cvsignore new file mode 100644 index 0000000000..800c7ed7b4 --- /dev/null +++ b/plug-ins/newsprint/.cvsignore @@ -0,0 +1,6 @@ +Makefile.in +Makefile +.deps +_libs +.libs +newsprint diff --git a/plug-ins/newsprint/Makefile.am b/plug-ins/newsprint/Makefile.am new file mode 100644 index 0000000000..1d050a1e62 --- /dev/null +++ b/plug-ins/newsprint/Makefile.am @@ -0,0 +1,35 @@ +## Process this file with automake to produce Makefile.in + +pluginlibdir = $(gimpplugindir)/plug-ins + +pluginlib_PROGRAMS = newsprint + +newsprint_SOURCES = \ + newsprint.c + +INCLUDES = \ + -I$(top_srcdir) \ + $(GTK_CFLAGS) \ + -I$(includedir) + +LDADD = \ + $(top_builddir)/libgimp/libgimp.la \ + $(GTK_LIBS) + +DEPS = \ + $(top_builddir)/libgimp/libgimp.la + +newsprint_DEPENDENCIES = $(DEPS) + +.PHONY: files + +files: + @files=`ls $(DISTFILES) 2> /dev/null`; for p in $$files; do \ + echo $$p; \ + done + @for subdir in $(SUBDIRS); do \ + files=`cd $$subdir; $(MAKE) files | grep -v "make\[[1-9]\]"`; \ + for file in $$files; do \ + echo $$subdir/$$file; \ + done; \ + done diff --git a/plug-ins/newsprint/newsprint.c b/plug-ins/newsprint/newsprint.c new file mode 100644 index 0000000000..904f27828e --- /dev/null +++ b/plug-ins/newsprint/newsprint.c @@ -0,0 +1,2237 @@ +/* The GIMP -- an image manipulation program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * newsprint plug-in + * Copyright (C) 1997-1998 Austin Donnelly + * + * 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. + * + * Portions of this plug-in are copyright 1991-1992 Adobe Systems + * Incorporated. See the spot_PS*() functions for details. + */ + +/* + * version 0.01 + * This version requires gtk-1.0.4 or above. + * + * This plug-in puts an image through a screen at a particular angle + * and lines per inch, to arrive at a newspaper-style effect. + * + * Austin Donnelly + * http://www.cl.cam.ac.uk/~and1000/newsprint/ + * + * Richard Mortier did the spot_round() function + * with correct tonal balance. + * + * Tim Harris provided valuable feedback on + * pre-press issues. + * + */ + + +#include +#include +#include +#include +#include "libgimp/gimp.h" + +#ifdef RCSID +static char rcsid[] = "$Id$"; +#endif + +#define VERSION "v0.01" + +/* Some useful macros */ +#ifdef DEBUG +#define DEBUG_PRINT(x) printf x +#else +#define DEBUG_PRINT(x) +#endif + +/* HACK so we compile with old gtks. Won't work on machines where the + * size of an int is different from the size of a void*, eg Alphas */ +#ifndef GINT_TO_POINTER +#warning glib did not define GINT_TO_POINTER, assuming same size as int +#define GINT_TO_POINTER(x) ((gpointer)(x)) +#endif + +#ifndef GPOINTER_TO_INT +#warning glib did not define GPOINTER_TO_INT, assuming same size as int +#define GPOINTER_TO_INT(x) ((int)(x)) +#endif + + +#define TIMINGS + +#ifdef TIMINGS +#include +#include +#define TM_INIT() struct timeval tm_start, tm_end +#define TM_START() gettimeofday(&tm_start, NULL) +#define TM_END() \ +do { \ + gettimeofday(&tm_end, NULL); \ + tm_end.tv_sec -= tm_start.tv_sec; \ + tm_end.tv_usec -= tm_start.tv_usec; \ + if (tm_end.tv_usec < 0) \ + { \ + tm_end.tv_usec += 1000000; \ + tm_end.tv_sec -= 1; \ + } \ + printf("operation took %ld.%06ld\n", tm_end.tv_sec, tm_end.tv_usec); \ +} while(0) +#else +#define TM_INIT() +#define TM_START() +#define TM_END() +#endif + +#define TILE_CACHE_SIZE 16 +#define ENTSCALE_SCALE_WIDTH 125 +#define ENTSCALE_ENTRY_WIDTH 40 +#define DEF_OVERSAMPLE 1 /* default for how much to oversample by */ +#define SPOT_PREVIEW_SZ 16 +#define PREVIEW_OVERSAMPLE 3 + + +#define BOUNDS(a,x,y) ((a < x) ? x : ((a > y) ? y : a)) +#define ISNEG(x) (((x) < 0)? 1 : 0) +#define DEG2RAD(d) ((d) * M_PI / 180) +#define VALID_BOOL(x) ((x) == TRUE || (x) == FALSE) +#define CLAMPED_ADD(a, b) (((a)+(b) > 0xff)? 0xff : (a) + (b)) + +/* Ideally, this would be in a common header file somewhere. This was + * nicked from app/convert.c */ +#define INTENSITY(r,g,b) (r * 0.30 + g * 0.59 + b * 0.11 + 0.001) + +/* Bartlett window supersampling weight function. See table 4.1, page + * 123 of Alan Watt and Mark Watt, Advanced Animation and Rendering + * Techniques, theory and practice. Addison-Wesley, 1992. ISBN + * 0-201-54412-1 */ +#define BARTLETT(x,y) (((oversample/2)+1-ABS(x)) * ((oversample/2)+1-ABS(y))) +#define WGT(x,y) wgt[((y+oversample/2)*oversample) + x+oversample/2] + +/* colourspaces we can separate to: */ +#define CS_GREY 0 +#define CS_RGB 1 +#define CS_CMYK 2 +#define CS_INTENSITY 3 +#define NUM_CS 4 +#define VALID_CS(x) ((x) >= 0 && (x) <= NUM_CS-1) + +/* Spot function related types and definitions */ + +typedef gdouble spotfn_t (gdouble x, gdouble y); + +/* forward declaration of the functions themselves */ +static spotfn_t spot_round; +static spotfn_t spot_line; +static spotfn_t spot_diamond; +static spotfn_t spot_PSsquare; +static spotfn_t spot_PSdiamond; + +typedef struct { + const char *name; /* function's name */ + spotfn_t *fn; /* function itself */ + guchar *thresh; /* cached threshold matrix */ + gdouble prev_lvl[3]; /* intensities to preview */ + guchar *prev_thresh; /* preview-sized threshold matrix */ + gint balanced; /* TRUE if spot fn is already balanced */ +} spot_info_t; + + +/* This is all the info needed per spot function. Functions are refered to + * by their index into this array. */ +static spot_info_t spotfn_list[] = { +#define SPOTFN_DOT 0 + {"round", + spot_round, + NULL, + {.22, .50, .90}, + NULL, + FALSE}, + + {"line", + spot_line, + NULL, + {.15, .50, .80}, + NULL, + FALSE}, + + {"diamond", + spot_diamond, + NULL, + {.15, .50, .80}, + NULL, + TRUE}, + + {"PS square (Euclidean dot)", + spot_PSsquare, + NULL, + {.15, .50, .90}, + NULL, + FALSE}, + + {"PS diamond", + spot_PSdiamond, + NULL, + {.15, .50, .90}, + NULL, + FALSE}, + + /* NULL-name terminates */ + {NULL, + NULL, + NULL, + {0.0, 0.0, 0.0}, + NULL, + FALSE} +}; + +#define NUM_SPOTFN ((sizeof(spotfn_list) / sizeof(spot_info_t)) - 1) +#define VALID_SPOTFN(x) ((x) >= 0 && (x) < NUM_SPOTFN) +#define THRESH(x,y) (thresh[(y)*width + (x)]) +#define THRESHn(n,x,y) ((thresh[n])[(y)*width + (x)]) + + + +/* Arguments to filter */ + +/* Some of these are here merely to save them across calls. They are + * marked as "UI use". Everything else is a valid arg. */ +typedef struct { + /* resolution section: */ + gint cell_width; + + /* screening section: */ + gint colourspace; /* 0: RGB, 1: CMYK, 2: Intensity */ + gint k_pullout; /* percentage of black to pull out */ + + /* grey screen (only used if greyscale drawable) */ + gdouble gry_ang; + gint gry_spotfn; + /* red / cyan screen */ + gdouble red_ang; + gint red_spotfn; + /* green / magenta screen */ + gdouble grn_ang; + gint grn_spotfn; + /* blue / yellow screen */ + gdouble blu_ang; + gint blu_spotfn; + + /* anti-alias section */ + gint oversample; /* 1 == no anti-aliasing, else small odd int */ +} NewsprintValues; + +/* bits of state used by the UI, but not visible from the PDB */ +typedef struct { + gint input_spi; /* input samples per inch */ + gdouble output_lpi; /* desired output lines per inch */ + gint lock_channels; /* changes to one channel affect all */ +} NewsprintUIValues; + +typedef struct { + gint run; +} NewsprintInterface; + + +typedef void (*EntscaleCallbackFunc) (gdouble new_val, gpointer data); + + +typedef enum { + ENTSCALE_INT, + ENTSCALE_DOUBLE +} EntscaleType; + + +typedef struct { + GtkObject *adjustment; + GtkWidget *entry; + GtkWidget *label; + GtkWidget *hbox; + EntscaleType type; + gchar fmt_string[16]; + gint constraint; + EntscaleCallbackFunc callback; + gpointer call_data; +} Entscale; + + + + +/* state for the preview widgets */ +typedef struct { + GtkWidget *widget; /* preview widget itself */ + GtkWidget *label; /* the label below it */ +} preview_st; + +/* state for the channel frames */ +typedef struct _channel_st { + GtkWidget *frame; /* frame around this channel */ + gint *spotfn_num; /* which spotfn the menu is controlling */ + preview_st prev[3]; /* state for 3 preview widgets */ + Entscale *entscale; /* angle entscale widget */ + GtkWidget *option_menu; /* popup for spot function */ + GtkWidget *menuitem[NUM_SPOTFN]; /* menuitems for each spot function */ + struct _channel_st *next; /* circular list of channels in locked group */ +} channel_st; + + +/* State associated with the configuration dialog and passed to its + * callback functions */ +typedef struct { + GtkWidget *dlg; /* main dialog itself */ + Entscale *pull; /* black pullout percentage */ + GtkWidget *vbox; /* container for screen info */ + /* room for up to 4 channels per colourspace */ + channel_st *chst[NUM_CS][4]; +} NewsprintDialog_st; + + + + +/***** Local vars *****/ + + +/* defaults */ +/* fixed copy so we can reset them */ +static const NewsprintValues factory_defaults = +{ + /* resolution stuff */ + 10, /* cell width */ + + /* screen setup (default is the classic rosette pattern) */ + CS_RGB, /* use RGB, not CMYK or Intensity */ + 100, /* max pullout */ + /* grey/black */ + 45.0, + SPOTFN_DOT, + /* red/cyan */ + 15.0, + SPOTFN_DOT, + /* green/magenta */ + 75.0, + SPOTFN_DOT, + /* blue/yellow */ + 0.0, + SPOTFN_DOT, + + /* anti-alias control */ + DEF_OVERSAMPLE +}; + +static const NewsprintUIValues factory_defaults_ui = { + 75, /* input spi */ + 7.5, /* output lpi */ + FALSE /* lock channels */ +}; + +/* Mutable copy for normal use. Initialised in run(). */ +static NewsprintValues pvals; +static NewsprintUIValues pvals_ui; + +static NewsprintInterface pint = +{ + FALSE /* run */ +}; + + +/* channel templates */ +typedef struct { + const gchar *name; + /* pointers to the variables this channel updates */ + gdouble *angle; + gint *spotfn; + /* factory defaults for the angle and spot function (as pointers so + * the silly compiler can see they're really constants) */ + const gdouble *factory_angle; + const gint *factory_spotfn; +} chan_tmpl; + +static const chan_tmpl grey_tmpl[] = { + {"Grey", + &pvals.gry_ang, + &pvals.gry_spotfn, + &factory_defaults.gry_ang, + &factory_defaults.gry_spotfn}, + + {NULL, NULL, NULL, NULL, NULL} +}; + +static const chan_tmpl rgb_tmpl[] = { + {"Red", + &pvals.red_ang, + &pvals.red_spotfn, + &factory_defaults.red_ang, + &factory_defaults.red_spotfn}, + + {"Green", + &pvals.grn_ang, + &pvals.grn_spotfn, + &factory_defaults.grn_ang, + &factory_defaults.grn_spotfn}, + + {"Blue", + &pvals.blu_ang, + &pvals.blu_spotfn, + &factory_defaults.blu_ang, + &factory_defaults.blu_spotfn}, + + {NULL, NULL, NULL, NULL, NULL} +}; + +static const chan_tmpl cmyk_tmpl[] = { + {"Cyan", + &pvals.red_ang, + &pvals.red_spotfn, + &factory_defaults.red_ang, + &factory_defaults.red_spotfn}, + + + {"Magenta", + &pvals.grn_ang, + &pvals.grn_spotfn, + &factory_defaults.grn_ang, + &factory_defaults.grn_spotfn}, + + {"Yellow", + &pvals.blu_ang, + &pvals.blu_spotfn, + &factory_defaults.blu_ang, + &factory_defaults.blu_spotfn}, + + {"Black", + &pvals.gry_ang, + &pvals.gry_spotfn, + &factory_defaults.gry_ang, + &factory_defaults.gry_spotfn}, + + {NULL, NULL, NULL, NULL, NULL} +}; + +static const chan_tmpl intensity_tmpl[] = { + {"Intensity", + &pvals.gry_ang, + &pvals.gry_spotfn, + &factory_defaults.gry_ang, + &factory_defaults.gry_spotfn}, + + {NULL, NULL, NULL, NULL, NULL} +}; + +/* cspace_chan_tmpl is indexed by colourspace, and gives an array of + * channel templates for that colourspace */ +static const chan_tmpl *cspace_chan_tmpl[] = { + grey_tmpl, + rgb_tmpl, + cmyk_tmpl, + intensity_tmpl +}; + +#define NCHANS(x) ((sizeof(x) / sizeof(chan_tmpl)) - 1) + +/* cspace_nchans gives a quick way of finding the number of channels + * in a colourspace. Alternatively, if you're walking the channel + * template, you can use the NULL entry at the end to stop. */ +static const gint cspace_nchans[] = { + NCHANS(grey_tmpl), + NCHANS(rgb_tmpl), + NCHANS(cmyk_tmpl), + NCHANS(intensity_tmpl) +}; + + +/* Declare local functions. */ +static void query (void); +static void run (gchar *name, + gint nparams, + GParam *param, + gint *nreturn_vals, + GParam **return_vals); + +static gint newsprint_dialog (GDrawable *drawable); +static void newsprint_close_callback (GtkWidget *widget, + gpointer data); +static void newsprint_ok_callback (GtkWidget *widget, + gpointer data); + +static void newsprint_toggle_update (GtkWidget *widget, + gpointer data); + +static void newsprint_cspace_update (GtkWidget *widget, + gpointer data); + +static void newsprint (GDrawable *drawable); + +static guchar * spot2thresh (gint type, gint width); + +static Entscale * entscale_new (GtkWidget *table, gint x, gint y, + gchar *caption, EntscaleType type, gpointer variable, + gdouble min, gdouble max, gdouble step, + gint constraint, EntscaleCallbackFunc callback, + gpointer call_data); + + +static int entscale_get_precision (gdouble step); +static void entscale_destroy_callback (GtkWidget *widget, gpointer data); +static void entscale_scale_update (GtkAdjustment *adjustment, gpointer data); +static void entscale_entry_update (GtkWidget *widget, gpointer data); + +static void entscale_set_sensitive (Entscale *ent, gint sensitive); +static void entscale_set_value(Entscale *ent, gfloat value); + + + +GPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run /* run_proc */ +}; + + + +/***** Functions *****/ + +MAIN () + +static void +query() +{ + static GParamDef args[]= + { + { PARAM_INT32, "run_mode", "Interactive, non-interactive" }, + { PARAM_IMAGE, "image", "Input image (unused)" }, + { PARAM_DRAWABLE, "drawable", "Input drawable" }, + + { PARAM_INT32, "cell_width", "screen cell width, in pixels" }, + + { PARAM_INT32, "colourspace", "separate to 0:RGB, 1:CMYK, 2:Intensity" }, + { PARAM_INT32, "k_pullout", "Percentage of black to pullout (CMYK only)" }, + + { PARAM_FLOAT, "gry_ang", "Grey/black screen angle (degrees)" }, + { PARAM_INT32, "gry_spotfn", "Grey/black spot function (0=dots, 1=lines, 2=diamonds, 3=euclidean dot, 4=PS diamond)" }, + { PARAM_FLOAT, "red_ang", "Red/cyan screen angle (degrees)" }, + { PARAM_INT32, "red_spotfn", "Red/cyan spot function (values as gry_spotfn)" }, + { PARAM_FLOAT, "grn_ang", "Green/magenta screen angle (degrees)" }, + { PARAM_INT32, "grn_spotfn", "Green/magenta spot function (values as gry_spotfn)" }, + { PARAM_FLOAT, "blu_ang", "Blue/yellow screen angle (degrees)" }, + { PARAM_INT32, "blu_spotfn", "Blue/yellow spot function (values as gry_spotfn)" }, + + { PARAM_INT32, "oversample", "how many times to oversample spot fn" } + /* 15 args */ + }; + static GParamDef *return_vals = NULL; + static gint nargs = sizeof (args) / sizeof (args[0]); + static gint nreturn_vals = 0; + + gimp_install_procedure ("plug_in_newsprint", + "Re-sample the image to give a newspaper-like " + "effect", + "Halftone the image, trading off " + "resolution to represent colours or grey levels " + "using the process described both in the PostScript " + "language definition, and also by Robert Ulichney, " + "\"Digital halftoning\", MIT Press, 1987.", + "Austin Donnelly", + "Austin Donnelly", + "1998 (" VERSION ")", + "/Filters/Misc/Newsprint", + "RGB*, GRAY*", + PROC_PLUG_IN, + nargs, nreturn_vals, + args, return_vals); +} + +static void +run (gchar *name, + gint nparams, + GParam *param, + gint *nreturn_vals, + GParam **return_vals) +{ + static GParam values[1]; + GDrawable *drawable; + GRunModeType run_mode; + GStatusType status = STATUS_SUCCESS; + + run_mode = param[0].data.d_int32; + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = PARAM_STATUS; + values[0].data.d_status = status; + + /* basic defaults */ + pvals = factory_defaults; + pvals_ui = factory_defaults_ui; + + /* Get the specified drawable */ + drawable = gimp_drawable_get (param[2].data.d_drawable); + + switch (run_mode) + { + case RUN_INTERACTIVE: + /* Possibly retrieve data */ + gimp_get_data ("plug_in_newsprint", &pvals); + gimp_get_data ("plug_in_newsprint_ui", &pvals_ui); + + /* First acquire information with a dialog */ + if (! newsprint_dialog (drawable)) + { + gimp_drawable_detach (drawable); + return; + } + break; + + case RUN_NONINTERACTIVE: + /* Make sure all the arguments are there! */ + if (nparams != 15) + { + status = STATUS_CALLING_ERROR; + break; + } + + pvals.cell_width = param[3].data.d_int32; + pvals.colourspace = param[4].data.d_int32; + pvals.k_pullout = param[5].data.d_int32; + pvals.gry_ang = param[6].data.d_float; + pvals.gry_spotfn = param[7].data.d_int32; + pvals.red_ang = param[8].data.d_float; + pvals.red_spotfn = param[9].data.d_int32; + pvals.grn_ang = param[10].data.d_float; + pvals.grn_spotfn = param[11].data.d_int32; + pvals.blu_ang = param[12].data.d_float; + pvals.blu_spotfn = param[13].data.d_int32; + pvals.oversample = param[14].data.d_int32; + + /* check values are within permitted ranges */ + if (!VALID_SPOTFN(pvals.gry_spotfn) || + !VALID_SPOTFN(pvals.red_spotfn) || + !VALID_SPOTFN(pvals.grn_spotfn) || + !VALID_SPOTFN(pvals.blu_spotfn) || + !VALID_CS(pvals.colourspace) || + pvals.k_pullout < 0 || pvals.k_pullout > 100) + status = STATUS_CALLING_ERROR; + + break; + + case RUN_WITH_LAST_VALS: + /* Possibly retrieve data */ + gimp_get_data ("plug_in_newsprint", &pvals); + break; + + default: + break; + } + + if (status == STATUS_SUCCESS) + { + /* Make sure that the drawable is gray or RGB color */ + if (gimp_drawable_color (drawable->id) || + gimp_drawable_gray (drawable->id)) + { + gimp_progress_init("Newsprintifing..."); + + /* set the tile cache size */ + gimp_tile_cache_ntiles(TILE_CACHE_SIZE); + + /* run the newsprint effect */ + newsprint(drawable); + + if (run_mode != RUN_NONINTERACTIVE) + gimp_displays_flush(); + + /* Store data */ + if (run_mode == RUN_INTERACTIVE) + { + gimp_set_data ("plug_in_newsprint", + &pvals, sizeof (NewsprintValues)); + gimp_set_data ("plug_in_newsprint_ui", + &pvals_ui, sizeof (NewsprintUIValues)); + } + } + else + { + /*gimp_message ("newsprint: cannot operate on indexed images");*/ + status = STATUS_EXECUTION_ERROR; + } + } + + values[0].data.d_status = status; + + gimp_drawable_detach(drawable); +} + + + + +/* create new menu state, and the preview widgets for it */ +static channel_st * +new_preview(gint *sfn) +{ + channel_st *st; + GtkWidget *preview; + GtkWidget *label; + gint i; + + st = g_new(channel_st, 1); + + st->spotfn_num = sfn; + + /* make the preview widgets */ + for(i=0; i < 3; i++) + { + preview = gtk_preview_new(GTK_PREVIEW_COLOR); + gtk_preview_size(GTK_PREVIEW(preview), + SPOT_PREVIEW_SZ*2 + 1, SPOT_PREVIEW_SZ*2 + 1); + gtk_widget_show(preview); + + label = gtk_label_new(""); + gtk_widget_show(label); + + st->prev[i].widget = preview; + st->prev[i].label = label; + /* st->prev[i].value changed by preview_update */ + } + + return st; +} + + +/* the popup menu "st" has changed, so the previews associated with it + * need re-calculation */ +static void +preview_update(channel_st *st) +{ + gint sfn = *(st->spotfn_num); + preview_st *prev; + gint i; + gint x; + gint y; + gint width = SPOT_PREVIEW_SZ * PREVIEW_OVERSAMPLE; + gint oversample = PREVIEW_OVERSAMPLE; + guchar *thresh; + gchar pct[12]; + guchar value; + guchar row[3 * (2 * SPOT_PREVIEW_SZ + 1)]; + + /* If we don't yet have a preview threshold matrix for this spot + * function, generate one now. */ + if (!spotfn_list[sfn].prev_thresh) + { + spotfn_list[sfn].prev_thresh = + spot2thresh(sfn, SPOT_PREVIEW_SZ*PREVIEW_OVERSAMPLE); + } + + + thresh = spotfn_list[sfn].prev_thresh; + + for(i=0; i < 3; i++) + { + prev = &st->prev[i]; + + value = spotfn_list[sfn].prev_lvl[i] * 0xff; + + for (y=0; y <= SPOT_PREVIEW_SZ*2; y++) + { + for (x=0; x <= SPOT_PREVIEW_SZ*2; x++) + { + guint32 sum = 0; + gint sx, sy; + gint tx, ty; + gint rx, ry; + + rx = x * PREVIEW_OVERSAMPLE; + ry = y * PREVIEW_OVERSAMPLE; + + for(sy=-oversample/2; sy<=oversample/2; sy++) + for(sx=-oversample/2; sx<=oversample/2; sx++) + { + tx = rx+sx; + ty = ry+sy; + while(tx < 0) tx += width; + while(ty < 0) ty += width; + while(tx >= width) tx -= width; + while(ty >= width) ty -= width; + + if (value > THRESH(tx, ty)) + sum += 0xff * BARTLETT(sx, sy); + } + sum /= BARTLETT(0,0) * BARTLETT(0,0); + + /* blue lines on cell boundaries */ + if ((x % SPOT_PREVIEW_SZ) == 0 || + (y % SPOT_PREVIEW_SZ) == 0) + { + row[x*3+0] = 0; + row[x*3+1] = 0; + row[x*3+2] = 0xff; + } + else + { + row[x*3+0] = sum; + row[x*3+1] = sum; + row[x*3+2] = sum; + } + } + + gtk_preview_draw_row(GTK_PREVIEW(prev->widget), + row, 0, y, SPOT_PREVIEW_SZ*2+1); + } + + /* redraw preview widget */ + gtk_widget_draw(prev->widget, NULL); + + sprintf(pct, "%2d%%", (int)rint(spotfn_list[sfn].prev_lvl[i] * 100)); + gtk_label_set(GTK_LABEL(prev->label), pct); + } + + gdk_flush (); +} + + +static void +newsprint_menu_callback(GtkWidget *widget, + gpointer data) +{ + channel_st *st = data; + gpointer *ud; + gint menufn; + static gint in_progress = FALSE; + + /* we shouldn't need recursion protection, but if lock_channels is + * set, and gtk_option_menu_set_history ever generates an + * "activated" signmal, then we'll get back here. So we've defensive. */ + if (in_progress) + { + printf("newsprint_menu_callback: unexpected recursion: " + "can't happen\n"); + return; + } + + in_progress = TRUE; + + ud = gtk_object_get_user_data(GTK_OBJECT(widget)); + menufn = (gint)ud; + + *(st->spotfn_num) = menufn; + + preview_update(st); + + /* ripple the change to the other popups if the channels are + * locked together. */ + if (pvals_ui.lock_channels) + { + channel_st *c = st->next; + gint oldfn; + + while(c != st) + { + gtk_option_menu_set_history(GTK_OPTION_MENU(c->option_menu), + menufn); + oldfn = *(c->spotfn_num); + *(c->spotfn_num) = menufn; + if (oldfn != menufn) + preview_update(c); + c = c->next; + } + } + + in_progress = FALSE; +} + + +static void +angle_callback(gdouble new_val, gpointer data) +{ + channel_st *st = data; + channel_st *c; + static gint in_progress = FALSE; + + if (pvals_ui.lock_channels && !in_progress) + { + in_progress = TRUE; + + c = st->next; + + while(c != st) + { + entscale_set_value(c->entscale, new_val); + c = c->next; + } + + in_progress = FALSE; + } +} + + + +static void +newsprint_defaults_callback(GtkWidget *widget, + gpointer data) +{ + NewsprintDialog_st *st = data; + gint saved_lock; + gint cspace; + channel_st **chst; + const chan_tmpl *ct; + gint spotfn; + gint c; + + /* temporarily turn off channel lock */ + saved_lock = pvals_ui.lock_channels; + pvals_ui.lock_channels = FALSE; + + /* for each colourspace, reset its channel info */ + for(cspace=0; cspace < NUM_CS; cspace++) + { + chst = st->chst[cspace]; + ct = cspace_chan_tmpl[cspace]; + + /* skip this colourspace if we haven't used it yet */ + if (!chst[0]) + continue; + + c = 0; + while(ct->name) + { + entscale_set_value(chst[c]->entscale, *(ct->factory_angle)); + + /* change the popup menu and also activate the menuitem in + * question, in order to run the handler that re-computes + * the preview area */ + spotfn = *(ct->factory_spotfn); + gtk_option_menu_set_history(GTK_OPTION_MENU(chst[c]->option_menu), + spotfn); + gtk_menu_item_activate(GTK_MENU_ITEM(chst[c]->menuitem[spotfn])); + + c++; + ct++; + } + } + + pvals_ui.lock_channels = saved_lock; +} + +/* Create (but don't yet show) a new frame for a channel 'widget'. + * Return the channel state, so caller can find the frame and place it + * in a container. */ +static channel_st * +new_channel(const chan_tmpl *ct) +{ + GtkWidget *table; + GtkWidget *label; + GtkWidget *menu; + spot_info_t *sf; + channel_st *chst; + gint i; + + /* create the channel state record */ + chst = new_preview(ct->spotfn); + + chst->frame = gtk_frame_new (ct->name); + gtk_frame_set_shadow_type (GTK_FRAME (chst->frame), GTK_SHADOW_ETCHED_IN); + gtk_container_border_width (GTK_CONTAINER (chst->frame), 4); + + table = gtk_table_new (3, 2, FALSE); + gtk_container_border_width (GTK_CONTAINER (table), 4); + gtk_container_add (GTK_CONTAINER (chst->frame), table); + + /* angle slider */ + chst->entscale = entscale_new(table, 0, 0, "angle", + ENTSCALE_DOUBLE, ct->angle, + -90.0, 90.0, 1.0, TRUE/*constrain*/, + angle_callback, chst); + + /* spot function popup */ + label = gtk_label_new("spot function"); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, + GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show(label); + + chst->option_menu = gtk_option_menu_new(); + gtk_table_attach(GTK_TABLE(table), chst->option_menu, 0, 1, 2, 3, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + gtk_widget_show(chst->option_menu); + + menu = gtk_menu_new(); + + sf = spotfn_list; + i = 0; + while(sf->name) + { + chst->menuitem[i] = gtk_menu_item_new_with_label(sf->name); + gtk_signal_connect(GTK_OBJECT(chst->menuitem[i]), "activate", + (GtkSignalFunc) newsprint_menu_callback, + chst); + gtk_object_set_user_data(GTK_OBJECT(chst->menuitem[i]), (gpointer*)i); + gtk_widget_show(chst->menuitem[i]); + gtk_menu_append(GTK_MENU(menu), GTK_WIDGET(chst->menuitem[i])); + sf++; + i++; + } + + gtk_menu_set_active(GTK_MENU(menu), *ct->spotfn); + + gtk_option_menu_set_menu(GTK_OPTION_MENU(chst->option_menu), menu); + gtk_widget_show(chst->option_menu); + + /* spot function previews go in table slots 1-2, 1-3 (double + * height) */ + { + GtkWidget *sub; + GtkWidget *align; + + sub = gtk_table_new(2, 3, FALSE); + gtk_container_border_width(GTK_CONTAINER(sub), 1); + + align = gtk_alignment_new(0.5, 1.0, 1.0, 0.0); + gtk_container_add(GTK_CONTAINER(align), sub); + gtk_widget_show(align); + + gtk_table_attach(GTK_TABLE(table), align, 1, 2, 1, 3, + GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); + + for(i=0; i < 3; i++) + { + gtk_table_attach(GTK_TABLE(sub), chst->prev[i].widget, + i, i+1, 0, 1, + GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); + + gtk_table_attach(GTK_TABLE(sub), chst->prev[i].label, + i, i+1, 1, 2, + GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); + } + + gtk_widget_show(sub); + } + + /* fire the update once to make sure we start with something + * in the preview windows */ + preview_update(chst); + + gtk_widget_show(table); + + /* deliberately don't show the chst->frame, leave that up to + * the caller */ + + return chst; +} + + +/* Make all the channels needed for "colourspace", and fill in + * the respective channel state fields in "st". */ +static void +gen_channels(NewsprintDialog_st *st, gint colourspace) +{ + const chan_tmpl *ct; + channel_st **chst; + channel_st *base = NULL; + gint i; + + chst = st->chst[colourspace]; + ct = cspace_chan_tmpl[colourspace]; + i = 0; + + while(ct->name) + { + chst[i] = new_channel(ct); + + if (i) + chst[i-1]->next = chst[i]; + else + base = chst[i]; + + gtk_box_pack_start(GTK_BOX(st->vbox), chst[i]->frame, + TRUE, FALSE, 0); + gtk_box_reorder_child(GTK_BOX(st->vbox), chst[i]->frame, 5+i); + + i++; + ct++; + } + + /* make the list circular */ + chst[i-1]->next = base; +} + + +static gint +newsprint_dialog (GDrawable *drawable) +{ + gchar **argv; + gint argc; + /* widgets we need from callbacks stored here */ + NewsprintDialog_st st; + GtkWidget *frame; + GtkWidget *table; + GtkWidget *align; + GtkWidget *button; + GtkWidget *hbox; + GtkWidget *toggle; + GtkWidget *label; + GSList *group = NULL; + gint bpp; + guchar *color_cube; + gint i; + + argc = 1; + argv = g_new(gchar *, 1); + argv[0] = g_strdup("newsprint"); + + gtk_init (&argc, &argv); + gtk_rc_parse(gimp_gtkrc()); + + gtk_preview_set_gamma (gimp_gamma ()); + gtk_preview_set_install_cmap (gimp_install_cmap ()); + color_cube = gimp_color_cube (); + gtk_preview_set_color_cube (color_cube[0], color_cube[1], + color_cube[2], color_cube[3]); + + gtk_widget_set_default_visual (gtk_preview_get_visual ()); + gtk_widget_set_default_colormap (gtk_preview_get_cmap ()); + +#if 0 + printf("newsprint: waiting... (pid %d)\n", getpid()); + kill(getpid(), 19); +#endif + + /* flag values to say we haven't filled these channel + * states in yet */ + for(i=0; iid); + if (gimp_drawable_has_alpha(drawable->id)) + bpp--; + + /* force greyscale if it's the only thing we can do */ + if (bpp == 1) { + pvals.colourspace = CS_GREY; + } else { + if (pvals.colourspace == CS_GREY) + pvals.colourspace = CS_RGB; + } + + st.dlg = gtk_dialog_new (); + gtk_window_set_title (GTK_WINDOW (st.dlg), "Newsprint"); + gtk_window_position (GTK_WINDOW (st.dlg), GTK_WIN_POS_MOUSE); + gtk_signal_connect (GTK_OBJECT (st.dlg), "destroy", + (GtkSignalFunc) newsprint_close_callback, + NULL); + + /* Action area */ + button = gtk_button_new_with_label ("OK"); + GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); + gtk_signal_connect (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) newsprint_ok_callback, + st.dlg); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (st.dlg)->action_area), button, + TRUE, TRUE, 0); + gtk_widget_grab_default (button); + gtk_widget_show (button); + + button = gtk_button_new_with_label ("Cancel"); + GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); + gtk_signal_connect_object (GTK_OBJECT (button), "clicked", + (GtkSignalFunc) gtk_widget_destroy, + GTK_OBJECT (st.dlg)); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (st.dlg)->action_area), button, + TRUE, TRUE, 0); + gtk_widget_show (button); + + /* resolution settings */ + frame = gtk_frame_new ("Resolution"); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN); + gtk_container_border_width (GTK_CONTAINER (frame), 10); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (st.dlg)->vbox), frame, + TRUE, TRUE, 0); + + table = gtk_table_new (3, 2, FALSE); + gtk_container_border_width (GTK_CONTAINER (table), 10); + gtk_container_add (GTK_CONTAINER (frame), table); + + entscale_new(table, 0, 0, "Input SPI ", + ENTSCALE_INT, &pvals_ui.input_spi, + 1.0, 1200.0, 5.0, FALSE/*constrain*/, + NULL, NULL); + + entscale_new(table, 0, 1, "Output LPI ", + ENTSCALE_DOUBLE, &pvals_ui.output_lpi, + 1.0, 1200.0, 5.0, FALSE/*constrain*/, + NULL, NULL); + + entscale_new(table, 0, 2, "Cell size ", + ENTSCALE_INT, &pvals.cell_width, + 3.0, 100.0, 1.0, FALSE/*constrain*/, + NULL, NULL); + /* XXX still need interlocks between these. Maybe use callback fns? */ + + gtk_widget_show(table); + gtk_widget_show(frame); + + + /* screen settings */ + frame = gtk_frame_new ("Screen"); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN); + gtk_container_border_width (GTK_CONTAINER (frame), 10); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (st.dlg)->vbox), frame, + TRUE, TRUE, 0); + + st.vbox = gtk_vbox_new(FALSE, 1); + gtk_container_add (GTK_CONTAINER (frame), st.vbox); + + /* optional portion begins */ + if (bpp != 1) + { + table = gtk_table_new (2, 2, FALSE); + gtk_container_border_width (GTK_CONTAINER (table), 10); + gtk_box_pack_start (GTK_BOX (st.vbox), table, TRUE, TRUE, 0); + + /* black pullout */ + st.pull = entscale_new(table, 0, 1, "Black pullout (%)", + ENTSCALE_INT, &pvals.k_pullout, + 0.0, 100.0, 1.0, TRUE/*constrain*/, + NULL, NULL); + entscale_set_sensitive(st.pull, (pvals.colourspace == CS_CMYK)); + + /* RGB / CMYK / Intensity select */ + label = gtk_label_new("Separate to"); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, + GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show(label); + + hbox = gtk_hbox_new(FALSE, 5); + + toggle = gtk_radio_button_new_with_label(group, "RGB"); + group = gtk_radio_button_group (GTK_RADIO_BUTTON (toggle)); + gtk_box_pack_start (GTK_BOX (hbox), toggle, TRUE, TRUE, 0); + gtk_object_set_user_data(GTK_OBJECT(toggle), &st); + gtk_signal_connect (GTK_OBJECT (toggle), "toggled", + (GtkSignalFunc) newsprint_cspace_update, + GINT_TO_POINTER(CS_RGB)); + gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (toggle), + (pvals.colourspace == CS_RGB)); + gtk_widget_show (toggle); + + toggle = gtk_radio_button_new_with_label (group, "CMYK"); + group = gtk_radio_button_group (GTK_RADIO_BUTTON (toggle)); + gtk_box_pack_start (GTK_BOX (hbox), toggle, TRUE, TRUE, 0); + gtk_object_set_user_data(GTK_OBJECT(toggle), &st); + gtk_signal_connect (GTK_OBJECT (toggle), "toggled", + (GtkSignalFunc) newsprint_cspace_update, + GINT_TO_POINTER(CS_CMYK)); + gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (toggle), + (pvals.colourspace == CS_CMYK)); + gtk_widget_show (toggle); + + toggle = gtk_radio_button_new_with_label (group, "Intensity"); + group = gtk_radio_button_group (GTK_RADIO_BUTTON (toggle)); + gtk_box_pack_start (GTK_BOX (hbox), toggle, TRUE, TRUE, 0); + gtk_object_set_user_data(GTK_OBJECT(toggle), &st); + gtk_signal_connect (GTK_OBJECT (toggle), "toggled", + (GtkSignalFunc) newsprint_cspace_update, + GINT_TO_POINTER(CS_INTENSITY)); + gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (toggle), + (pvals.colourspace == CS_INTENSITY)); + gtk_widget_show (toggle); + + gtk_table_attach(GTK_TABLE(table), hbox, 1, 2, 0, 1, + GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show(hbox); + + gtk_widget_show(table); + + /* channel lock & factory defaults button */ + align = gtk_alignment_new(0.0, 1.0, 0.1, 1.0); + hbox = gtk_hbox_new(TRUE, 10); + gtk_container_add(GTK_CONTAINER(align), hbox); + gtk_box_pack_start(GTK_BOX(st.vbox), align, TRUE, FALSE, 0); + /* make sure it went in the right place, since colourspace + * radio button callbacks may have already inserted the + * channel frames */ + gtk_box_reorder_child(GTK_BOX(st.vbox), align, 1); + gtk_widget_show(align); + gtk_widget_show(hbox); + + toggle = gtk_check_button_new_with_label("Lock channels"); + gtk_signal_connect(GTK_OBJECT(toggle), "toggled", + (GtkSignalFunc) newsprint_toggle_update, + &pvals_ui.lock_channels); + gtk_object_set_user_data(GTK_OBJECT(toggle), NULL); + gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON (toggle), + pvals_ui.lock_channels); + gtk_box_pack_start(GTK_BOX(hbox), toggle, TRUE, TRUE, 0); + gtk_widget_show(toggle); + + button = gtk_button_new_with_label("Factory defaults"); + gtk_signal_connect(GTK_OBJECT(button), "clicked", + (GtkSignalFunc) newsprint_defaults_callback, + &st); + gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0); + gtk_widget_show(button); + } + + /* Make the channels appropriate for this colourspace and + * currently selected defaults. They may have already been + * created as a result of callbacks to cspace_update from + * gtk_toggle_button_set_state(). Other channel frames are + * created lazily the first time they are required. */ + if (!st.chst[pvals.colourspace][0]) + { + channel_st **chst; + + gen_channels(&st, pvals.colourspace); + + chst = st.chst[pvals.colourspace]; + + for(i=0; i < cspace_nchans[pvals.colourspace]; i++) + gtk_widget_show(GTK_WIDGET(chst[i]->frame)); + } + + gtk_widget_show(st.vbox); + gtk_widget_show(frame); + + + + /* anti-alias control */ + frame = gtk_frame_new ("Anti-alias"); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN); + gtk_container_border_width (GTK_CONTAINER (frame), 10); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (st.dlg)->vbox), frame, + TRUE, TRUE, 0); + + table = gtk_table_new (1, 2, FALSE); + gtk_container_border_width (GTK_CONTAINER (table), 10); + gtk_container_add (GTK_CONTAINER (frame), table); + + entscale_new(table, 1, 0, "oversample ", + ENTSCALE_INT, &pvals.oversample, + 1.0, 15.0, 1.0, TRUE/*constrain*/, + NULL, NULL); + + gtk_widget_show (table); + gtk_widget_show (frame); + + gtk_widget_show (st.dlg); + + gtk_main (); + gdk_flush (); + + return pint.run; +} + +/* Newsprint interface functions */ + +static void +newsprint_close_callback (GtkWidget *widget, + gpointer data) +{ + gtk_main_quit (); +} + +static void +newsprint_ok_callback (GtkWidget *widget, + gpointer data) +{ + pint.run = TRUE; + gtk_widget_destroy (GTK_WIDGET (data)); +} + +static void +newsprint_toggle_update (GtkWidget *widget, + gpointer data) +{ + int *toggle_val; + + toggle_val = (int *) data; + + *toggle_val = GTK_TOGGLE_BUTTON(widget)->active; +} + + +static void +newsprint_cspace_update (GtkWidget *widget, + gpointer data) +{ + NewsprintDialog_st *st; + gint new_cs = GPOINTER_TO_INT(data); + gint old_cs = pvals.colourspace; + channel_st **chst; + gint i; + + st = gtk_object_get_user_data(GTK_OBJECT(widget)); + + if (!st) + printf("newsprint: cspace_update: no state, can't happen!\n"); + + if (st) + { + /* the CMYK widget looks after the black pullout widget */ + if (new_cs == CS_CMYK) + { + entscale_set_sensitive(st->pull, + GTK_TOGGLE_BUTTON(widget)->active); + } + + /* if we're not activate, then there's nothing more to do */ + if (!GTK_TOGGLE_BUTTON(widget)->active) + return; + + pvals.colourspace = new_cs; + + /* make sure we have the necessary channels for the new + * colourspace */ + if (!st->chst[new_cs][0]) + gen_channels(st, new_cs); + + /* hide the old channels (if any) */ + if (st->chst[old_cs][0]) + { + chst = st->chst[old_cs]; + + for(i=0; i < cspace_nchans[old_cs]; i++) + gtk_widget_hide(GTK_WIDGET(chst[i]->frame)); + } + + /* show the new channels */ + chst = st->chst[new_cs]; + for(i=0; i < cspace_nchans[new_cs]; i++) + gtk_widget_show(GTK_WIDGET(chst[i]->frame)); + } +} + + + + +/* + * Newsprint Effect + */ + + +/*************************************************************/ +/* Spot functions */ + + +/* Spot functions define the order in which pixels should be whitened + * as a cell lightened in colour. They are defined over the entire + * cell, and are called over each pixel in the cell. The cell + * co-ordinate space ranges from -1.0 .. +1.0 inclusive, in both x- and + * y-axes. + * + * This means the spot function f(x, y) must be defined for: + * -1 <= x <= +1, where x is a real number, and + * -1 <= y <= +1, where y is a real number. + * + * The function f's range is -1.0 .. +1.0 inclusive, but it is + * permissible for f to return values outside this range: the nearest + * valid value will be used instead. NOTE: this is in contrast with + * PostScript spot functions, where it is a RangeError for the spot + * function to go outside these limits. + * + * An initially black cell is filled from lowest spot function value + * to highest. The actual values returned do not matter - it is their + * relative orderings that count. This means that spot functions do + * not need to be tonally balanced. A tonally balanced spot function + * is one which for all slices though the function (eg say at z), the + * area of the slice = 4z. In particular, almost all PostScript spot + * functions are _not_ tonally balanced. + */ + + +/* The classic growing dot spot function. This one isn't tonally + * balanced. It can be made so, but it's _really_ ugly. Thanks to + * Richard Mortier for this one: + * + * #define a(r) \ + * ((r<=1)? M_PI * (r*r) : \ + * 4 * sqrt(r*r - 1) + M_PI*r*r - 4*r*r*acos(1/r)) + * + * radius = sqrt(x*x + y*y); + * + * return a(radius) / 4; */ +static gdouble +spot_round(gdouble x, gdouble y) +{ + return 1 - (x*x + y*y); +} + + +/* Another commonly seen spot function is the v-shaped wedge. Tonally + * balanced. */ +static gdouble +spot_line(gdouble x, gdouble y) +{ + return ABS(y); +} + + +/* Square/Diamond dot that never becomes round. Tonally balanced. */ +static gdouble +spot_diamond(gdouble x, gdouble y) +{ + gdouble xy = ABS(x) + ABS(y); + /* spot only valid for 0 <= xy <= 2 */ + return ((xy <= 1)? 2*xy*xy : 2*xy*xy - 4*(xy-1)*(xy-1)) / 4; +} + + + +/* The following functions were derived from a peice of PostScript by + * Peter Fink and published in his book, "PostScript Screening: Adobe + * Accurate Screens" (Adobe Press, 1992). Adobe Systems Incorporated + * allow its use, provided the following copyright message is present: + * + * % Film Test Pages for Screenset Development + * % Copyright (c) 1991 and 1992 Adobe Systems Incorporated + * % All rights reserved. + * % + * % NOTICE: This code is copyrighted by Adobe Systems Incorporated, and + * % may not be reproduced for sale except by permission of Adobe Systems + * % Incorporated. Adobe Systems Incorporated grants permission to use + * % this code for the development of screen sets for use with Adobe + * % Accurate Screens software, as long as the copyright notice remains + * % intact. + * % + * % By Peter Fink 1991/1992 + */ + +/* Square (or Euclidean) dot. Also very common. */ +static gdouble +spot_PSsquare(gdouble x, gdouble y) +{ + gdouble ax = ABS(x); + gdouble ay = ABS(y); + + return (ax+ay)>1? ((ay-1)*(ay-1) + (ax-1)*(ax-1)) - 1 : 1-(ay*ay + ax*ax); +} + +/* Diamond spot function, again from Peter Fink's PostScript + * original. Copyright as for previous function. */ +static gdouble +spot_PSdiamond(gdouble x, gdouble y) +{ + gdouble ax = ABS(x); + gdouble ay = ABS(y); + + return (ax+ay)<=0.75? 1-(ax*ax + ay*ay) : /* dot */ + ( (ax+ay)<=1.23? 1-((ay*0.76) + ax) : /* to diamond (0.76 distort) */ + ((ay-1)*(ay-1) + (ax-1)*(ax-1)) -1); /* back to dot */ +} + +/* end of Adobe Systems Incorporated copyrighted functions */ + + + + + +/*************************************************************/ +/* Spot function to threshold matrix conversion */ + + +/* Each call of the spot function results in one of these */ +typedef struct { + gint index; /* (y * width) + x */ + gdouble value; /* return value of the spot function */ +} order_t; + +/* qsort(3) compare function */ +static int +order_cmp(const void *va, const void *vb) +{ + const order_t *a = va; + const order_t *b = vb; + + return (a->value < b->value)? -1 : ((a->value > b->value)? +1 : 0); +} + +/* Convert spot function "type" to a threshold matrix of size "width" + * times "width". Returns newly allocated threshold matrix. The + * reason for qsort()ing the results rather than just using the spot + * function's value directly as the threshold value is that we want to + * ensure that the threshold matrix is tonally balanced - that is, for + * a threshold value of x%, x% of the values in the matrix are < x%. + * + * Actually, it turns out that qsort()ing a function which is already + * balanced can quite significantly detract from the quality of the + * final result. This is particularly noticable with the line or + * diamond spot functions at 45 degrees. This is because if the spot + * function has multiple locations with the same value, qsort may use + * them in any order. Often, there is quite clearly an optimal order + * however. By marking functions as pre-balanced, this random + * shuffling is avoided. WARNING: a non-balanced spot function marked + * as pre-balanced is bad: you'll end up with dark areas becoming too + * dark or too light, and vice versa for light areas. This is most + * easily checked by halftoning an area, then bluring it back - you + * should get the same colour back again. The only way of getting a + * correctly balanced function is by getting a formula for the spot's + * area as a function of x and y - this can be fairly tough (ie + * possiblly an integral in two dimensions that must be solved + * analytically). + * + * The threshold matrix is used to compare against image values. If + * the image value is greater than the threshold value, then the + * output pixel is illuminated. This means that a threshold matrix + * entry of 0 never causes output pixels to be illuminated. */ +static guchar * +spot2thresh(gint type, gint width) +{ + gdouble sx, sy; + gdouble val; + spotfn_t *spotfn; + guchar *thresh; + order_t *order; + gint x, y; + gint i; + gint wid2 = width*width; + gint balanced = spotfn_list[type].balanced; + + thresh = g_new(guchar, wid2); + spotfn = spotfn_list[type].fn; + + order = g_new(order_t, wid2); + + i = 0; + for(y=0; yid, &x1, &y1, &x2, &y2); + + bpp = gimp_drawable_bpp(drawable->id); + has_alpha = gimp_drawable_has_alpha(drawable->id); + colour_bpp = has_alpha? bpp-1 : bpp; + colourspace= pvals.colourspace; + if (bpp == 1) { + colourspace = CS_GREY; + } else { + if (colourspace == CS_GREY) + colourspace = CS_RGB; + } + + /* Bartlett window matrix optimisation */ + w002 = BARTLETT(0,0) * BARTLETT(0,0); +#if 0 + /* It turns out to be slightly slower to cache a pre-computed + * bartlett matrix! I put it down to d-cache pollution *shrug* */ + wgt = g_new(gint, oversample*oversample); + for(y=-oversample/2; y<=oversample/2; y++) + for(x=-oversample/2; x<=oversample/2; x++) + WGT(x,y) = BARTLETT(x,y); +#endif /* 0 */ + +#define ASRT(_x) \ +do { \ + if (!VALID_SPOTFN(_x)) \ + { \ + printf("newsprint: %d is not a valid spot type\n", _x); \ + _x = SPOTFN_DOT; \ + } \ +} while(0) + + /* calculate the RGB / CMYK rotations and threshold matrices */ + if (bpp == 1 || colourspace == CS_INTENSITY) + { + rot[0] = DEG2RAD(pvals.gry_ang); + thresh[0] = spot2thresh(pvals.gry_spotfn, width); + } + else + { + gint rf = pvals.red_spotfn; + gint gf = pvals.grn_spotfn; + gint bf = pvals.blu_spotfn; + + rot[0] = DEG2RAD(pvals.red_ang); + rot[1] = DEG2RAD(pvals.grn_ang); + rot[2] = DEG2RAD(pvals.blu_ang); + + /* always need at least one threshold matrix */ + ASRT(rf); + spotfn_list[rf].thresh = spot2thresh(rf, width); + thresh[0] = spotfn_list[rf].thresh; + + /* optimisation: only use separate threshold matrices if the + * spot functions are actually different */ + ASRT(gf); + if (!spotfn_list[gf].thresh) + spotfn_list[gf].thresh = spot2thresh(gf, width); + thresh[1] = spotfn_list[gf].thresh; + + ASRT(bf); + if (!spotfn_list[bf].thresh) + spotfn_list[bf].thresh = spot2thresh(bf, width); + thresh[2] = spotfn_list[bf].thresh; + + if (colourspace == CS_CMYK) + { + rot[3] = DEG2RAD(pvals.gry_ang); + gf = pvals.gry_spotfn; + ASRT(gf); + if (!spotfn_list[gf].thresh) + spotfn_list[gf].thresh = spot2thresh(gf, width); + thresh[3] = spotfn_list[gf].thresh; + } + } + + + + /* Initialize progress */ + progress = 0; + max_progress = (x2 - x1) * (y2 - y1); + + TM_START(); + + for( y = y1; y < y2; y += tile_width - ( y % tile_width ) ) + { + for( x = x1; x < x2; x += tile_width - ( x % tile_width ) ) + { + /* snap to tile boundary */ + x_step = tile_width - ( x % tile_width ); + y_step = tile_width - ( y % tile_width ); + /* don't step off the end of the image */ + x_step = MIN( x_step, x2-x ); + y_step = MIN( y_step, y2-y ); + + /* set up the source and dest regions */ + gimp_pixel_rgn_init (&src_rgn, drawable, x, y, x_step, y_step, + FALSE/*dirty*/, FALSE/*shadow*/); + + gimp_pixel_rgn_init (&dest_rgn, drawable, x, y, x_step, y_step, + TRUE/*dirty*/, TRUE/*shadow*/); + + /* page in the image, one tile at a time */ + for (pr = gimp_pixel_rgns_register (2, &src_rgn, &dest_rgn); + pr != NULL; + pr = gimp_pixel_rgns_process (pr)) + { + src_row = src_rgn.data; + dest_row = dest_rgn.data; + + for (row = 0; row < src_rgn.h; row++) + { + src = src_row; + dest = dest_row; + for (col = 0; col < src_rgn.w; col++) + { + guchar data[4]; + + rx = (x + col) * oversample; + ry = (y + row) * oversample; + + /* convert rx and ry to polar (r, theta) */ + r = sqrt(((double)rx)*rx + ((double)ry)*ry); + theta = atan2(((gdouble)ry), ((gdouble)rx)); + + for(b=0; b= width) tx -= width; + while (ty >= width) ty -= width; + if (data[b] > THRESHn(b, tx, ty)) + sum += 0xff * BARTLETT(sx, sy); + } + sum /= w002; + data[b] = sum; + } + } + if (has_alpha) + dest[colour_bpp] = src[colour_bpp]; + + /* re-pack the colours into RGB */ + switch(colourspace) { + case CS_CMYK: + data[0] = CLAMPED_ADD(data[0], data[3]); + data[1] = CLAMPED_ADD(data[1], data[3]); + data[2] = CLAMPED_ADD(data[2], data[3]); + data[0] = 0xff - data[0]; + data[1] = 0xff - data[1]; + data[2] = 0xff - data[2]; + break; + + case CS_INTENSITY: + if (has_alpha) + { + dest[colour_bpp] = data[0]; + data[0] = 0xff; + } + data[1] = data[1] * data[0] / 0xff; + data[2] = data[2] * data[0] / 0xff; + data[0] = data[3] * data[0] / 0xff; + break; + + default: + /* no other special cases */ + break; + } + + for(b=0; bid, TRUE); + gimp_drawable_update (drawable->id, x1, y1, (x2 - x1), (y2 - y1)); +} + + + + +/**************************************************************/ +/* Support routines */ + +/* Lightly modified entscale: + * o Added entscale_set_sensitive() method */ + + +/*************************************************************************/ +/** **/ +/** +++ Entscale **/ +/** **/ +/*************************************************************************/ + +/* these routines are taken from gflare.c */ + +/* -*- mode:c -*- */ +/* + Entry and Scale pair (int and double integrated) + + This is an attempt to combine entscale_int and double. + Never compiled yet. + + TODO: + - Do the proper thing when the user changes value in entry, + so that callback should not be called when value is actually not changed. + - Update delay + */ + + +static void entscale_destroy_callback (GtkWidget *widget, + gpointer data); +static void entscale_scale_update (GtkAdjustment *adjustment, + gpointer data); +static void entscale_entry_update (GtkWidget *widget, + gpointer data); +/* + * Create an entry, a scale and a label, then attach them to + * specified table. + * 1 row and 2 cols of table are needed. + * + * Input: + * table: table which entscale is attached to + * x, y: starting row and col in table + * caption: label string + * type: type of variable (ENTSCALE_INT or ENTSCALE_DOUBLE) + * variable: pointer to variable + * min, max: boundary of scale + * step: step of scale (ignored when (type == ENTSCALE_INT)) + * constraint: (bool) true iff the value of *variable should be + * constraint by min and max + * callback: called when the value is actually changed + * (*variable is automatically changed, so there's no + * need of callback func usually.) + * call_data: data for callback func + */ + +Entscale * +entscale_new (GtkWidget *table, gint x, gint y, gchar *caption, + EntscaleType type, gpointer variable, + gdouble min, gdouble max, gdouble step, + gint constraint, + EntscaleCallbackFunc callback, + gpointer call_data) +{ + Entscale *entscale; + GtkWidget *entry; + GtkWidget *scale; + GtkObject *adjustment; + gchar buffer[256]; + gdouble val; + gdouble constraint_val; + + entscale = g_new0 (Entscale, 1 ); + entscale->type = type; + entscale->constraint = constraint; + entscale->callback = callback; + entscale->call_data = call_data; + + switch (type) + { + case ENTSCALE_INT: + step = 1.0; + strcpy (entscale->fmt_string, "%.0f"); + val = *(gint *)variable; + break; + case ENTSCALE_DOUBLE: + sprintf (entscale->fmt_string, "%%.%df", entscale_get_precision (step) + 1); + val = *(gdouble *)variable; + break; + default: + g_error ("TYPE must be either ENTSCALE_INT or ENTSCALE_DOUBLE"); + val = 0; + break; + } + + entscale->label = gtk_label_new (caption); + gtk_misc_set_alignment (GTK_MISC (entscale->label), 0.0, 0.5); + + /* + If the first arg of gtk_adjustment_new() isn't between min and + max, it is automatically corrected by gtk later with + "value_changed" signal. I don't like this, since I want to leave + *variable untouched when `constraint' is false. + The lines below might look oppositely, but this is OK. + */ + if (constraint) + constraint_val = val; + else + constraint_val = BOUNDS (val, min, max); + + adjustment = entscale->adjustment = + gtk_adjustment_new (constraint_val, min, max, step, step, 0.0); + scale = gtk_hscale_new (GTK_ADJUSTMENT (adjustment)); + gtk_widget_set_usize (scale, ENTSCALE_SCALE_WIDTH, 0); + gtk_scale_set_draw_value (GTK_SCALE (scale), FALSE); + + entry = entscale->entry = gtk_entry_new (); + gtk_widget_set_usize (entry, ENTSCALE_ENTRY_WIDTH, 0); + sprintf (buffer, entscale->fmt_string, val); + gtk_entry_set_text (GTK_ENTRY (entry), buffer); + + /* entscale is done */ + gtk_object_set_user_data (GTK_OBJECT(adjustment), entscale); + gtk_object_set_user_data (GTK_OBJECT(entry), entscale); + + /* now ready for signals */ + gtk_signal_connect (GTK_OBJECT (entry), "changed", + (GtkSignalFunc) entscale_entry_update, + variable); + gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed", + (GtkSignalFunc) entscale_scale_update, + variable); + gtk_signal_connect (GTK_OBJECT (entry), "destroy", + (GtkSignalFunc) entscale_destroy_callback, + NULL ); + + /* start packing */ + entscale->hbox = gtk_hbox_new (FALSE, 5); + gtk_box_pack_start (GTK_BOX (entscale->hbox), scale, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (entscale->hbox), entry, FALSE, TRUE, 0); + + gtk_table_attach (GTK_TABLE (table), entscale->label, x, x+1, y, y+1, + GTK_FILL, GTK_FILL, 0, 0); + gtk_table_attach (GTK_TABLE (table), entscale->hbox, x+1, x+2, y, y+1, + GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); + + gtk_widget_show (entscale->label); + gtk_widget_show (entry); + gtk_widget_show (scale); + gtk_widget_show (entscale->hbox); + + return entscale; +} + +static int +entscale_get_precision (gdouble step) +{ + int precision; + if (step <= 0) + return 0; + for (precision = 0; pow (0.1, precision) > step; precision++) ; + return precision; +} + +static void +entscale_destroy_callback (GtkWidget *widget, + gpointer data) +{ + Entscale *entscale; + + entscale = gtk_object_get_user_data (GTK_OBJECT (widget)); + g_free (entscale); +} +static void +entscale_scale_update (GtkAdjustment *adjustment, + gpointer data) +{ + Entscale *entscale; + GtkEntry *entry; + gchar buffer[256]; + gdouble old_val, new_val; + + entscale = gtk_object_get_user_data (GTK_OBJECT (adjustment)); + + new_val = adjustment->value; + /* adjustmet->value is always constrainted */ + + switch (entscale->type) + { + case ENTSCALE_INT: + old_val = *(gint*) data; + *(gint*) data = (gint) new_val; + DEBUG_PRINT (("entscale_scale_update(int): fmt=\"%s\" old=%g new=%g\n", + entscale->fmt_string, old_val, new_val)); + break; + case ENTSCALE_DOUBLE: + old_val = *(gdouble*) data; + *(gdouble*) data = new_val; + DEBUG_PRINT (("entscale_scale_update(double): fmt=\"%s\" old=%g new=%g\n", + entscale->fmt_string, old_val, new_val)); + break; + default: + g_warning ("entscale_scale_update: invalid type"); + return; + } + + entry = GTK_ENTRY (entscale->entry); + sprintf (buffer, entscale->fmt_string, new_val); + + /* avoid infinite loop (scale, entry, scale, entry ...) */ + gtk_signal_handler_block_by_data (GTK_OBJECT(entry), data); + gtk_entry_set_text (entry, buffer); + gtk_signal_handler_unblock_by_data (GTK_OBJECT(entry), data); + + if (entscale->callback && (old_val != new_val)) + (*entscale->callback) (new_val, entscale->call_data); +} + +static void +entscale_entry_update (GtkWidget *widget, + gpointer data) +{ + Entscale *entscale; + GtkAdjustment *adjustment; + gdouble old_val, val, new_val, constraint_val; + + entscale = gtk_object_get_user_data (GTK_OBJECT (widget)); + adjustment = GTK_ADJUSTMENT (entscale->adjustment); + + val = atof (gtk_entry_get_text (GTK_ENTRY (widget))); + + constraint_val = BOUNDS (val, adjustment->lower, adjustment->upper); + + if (entscale->constraint) + new_val = constraint_val; + else + new_val = val; + + switch (entscale->type) + { + case ENTSCALE_INT: + old_val = *(gint*) data; + *(gint*) data = (gint) new_val; + DEBUG_PRINT (("entscale_entry_update(int): old=%g new=%g const=%g\n", + old_val, new_val, constraint_val)); + break; + case ENTSCALE_DOUBLE: + old_val = *(gdouble*) data; + *(gdouble*) data = new_val; + DEBUG_PRINT (("entscale_entry_update(double): old=%g new=%g const=%g\n", + old_val, new_val, constraint_val)); + break; + default: + g_warning ("entscale_entry_update: invalid type"); + return; + } + + adjustment->value = constraint_val; + /* avoid infinite loop (scale, entry, scale, entry ...) */ + gtk_signal_handler_block_by_data (GTK_OBJECT(adjustment), data ); + gtk_signal_emit_by_name (GTK_OBJECT(adjustment), "value_changed"); + gtk_signal_handler_unblock_by_data (GTK_OBJECT(adjustment), data ); + + if (entscale->callback && (old_val != new_val)) + (*entscale->callback) (new_val, entscale->call_data); +} + + +static void entscale_set_sensitive(Entscale *ent, gint sensitive) +{ + gtk_widget_set_sensitive(GTK_WIDGET(ent->label), sensitive); + gtk_widget_set_sensitive(GTK_WIDGET(ent->hbox), sensitive); +} + + +static void entscale_set_value(Entscale *ent, gfloat value) +{ + gtk_adjustment_set_value(GTK_ADJUSTMENT(ent->adjustment), value); +} diff --git a/plug-ins/wind/.cvsignore b/plug-ins/wind/.cvsignore new file mode 100644 index 0000000000..0448b4f834 --- /dev/null +++ b/plug-ins/wind/.cvsignore @@ -0,0 +1,6 @@ +Makefile.in +Makefile +.deps +_libs +.libs +wind diff --git a/plug-ins/wind/Makefile.am b/plug-ins/wind/Makefile.am new file mode 100644 index 0000000000..1072e2845e --- /dev/null +++ b/plug-ins/wind/Makefile.am @@ -0,0 +1,35 @@ +## Process this file with automake to produce Makefile.in + +pluginlibdir = $(gimpplugindir)/plug-ins + +pluginlib_PROGRAMS = wind + +wind_SOURCES = \ + wind.c + +INCLUDES = \ + -I$(top_srcdir) \ + $(GTK_CFLAGS) \ + -I$(includedir) + +LDADD = \ + $(top_builddir)/libgimp/libgimp.la \ + $(GTK_LIBS) + +DEPS = \ + $(top_builddir)/libgimp/libgimp.la + +wind_DEPENDENCIES = $(DEPS) + +.PHONY: files + +files: + @files=`ls $(DISTFILES) 2> /dev/null`; for p in $$files; do \ + echo $$p; \ + done + @for subdir in $(SUBDIRS); do \ + files=`cd $$subdir; $(MAKE) files | grep -v "make\[[1-9]\]"`; \ + for file in $$files; do \ + echo $$subdir/$$file; \ + done; \ + done diff --git a/plug-ins/wind/wind.c b/plug-ins/wind/wind.c new file mode 100644 index 0000000000..f25371c040 --- /dev/null +++ b/plug-ins/wind/wind.c @@ -0,0 +1,1105 @@ +/* + * wind - a plug-in for the GIMP + * + * Copyright (C) Nigel Wetten + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Contact info: nigel@cs.nwu.edu + * + * Version: 1.0.0 + */ + +#include +#include + +#include +#include "libgimp/gimp.h" + + +#define PLUG_IN_NAME "wind" + +#define COMPARE_WIDTH 3 + +#define ENTRY_WIDTH 40 +#define SCALE_WIDTH 200 +#define MIN_THRESHOLD 0 +#define MAX_THRESHOLD 50 +#define MIN_STRENGTH 1 +#define MAX_STRENGTH 50 + +#define NEGATIVE_STRENGTH_TEXT "\n Wind Strength must be greater than 0. \n" +#define THRESHOLD_TEXT "Higher values restrict the effect to fewer areas of the image" +#define STRENGTH_TEXT "Higher values increase the magnitude of the effect" +#define WIND_TEXT "A fine grained algorithm" +#define BLAST_TEXT "A coarse grained algorithm" +#define LEFT_TEXT "Makes the wind come from the left" +#define RIGHT_TEXT "Makes the wind come from the right" +#define LEADING_TEXT "The effect is applied at the leading edge of objects" +#define TRAILING_TEXT "The effect is applied at the trailing edge of objects" +#define BOTH_TEXT "The effect is applied at both edges of objects" + +typedef enum {LEFT, RIGHT} direction_t; +typedef enum {RENDER_WIND, RENDER_BLAST} algorithm_t; +typedef enum {BOTH, LEADING, TRAILING} edge_t; + + +static void query(void); +static void run(char *name, int nparams, GParam *param, + int *nreturn_vals, GParam **return_vals); +static void dialog_box(void); +static gint render_effect(GDrawable *drawable); +static void render_wind(GDrawable *drawable, gint threshold, gint strength, + direction_t direction, edge_t edge); +static void render_blast(GDrawable *drawable, gint threshold, gint strength, + direction_t direction, edge_t edge); +static gint render_blast_row(guchar *buffer, gint bytes, gint lpi, gint threshold, + gint strength, edge_t edge); +static void render_wind_row(guchar *sb, gint bytes, gint lpi, gint threshold, + gint strength, edge_t edge); +static void msg_ok_callback(GtkWidget *widget, gpointer data); +static void msg_close_callback(GtkWidget *widget, gpointer data); +static void close_callback(GtkWidget *widget, gpointer data); +static void ok_callback(GtkWidget *widget, gpointer data); +static void entry_callback(GtkWidget *widget, gpointer data); +static void radio_button_alg_callback(GtkWidget *widget, gpointer data); +static void radio_button_direction_callback(GtkWidget *widget, gpointer data); +static void get_derivative(guchar *pixel_R1, guchar *pixel_R2, + edge_t edge, gint *derivative_R, + gint *derivative_G, gint *derivative_B); +static gint threshold_exceeded(guchar *pixel_R1, guchar *pixel_R2, + edge_t edge, gint threshold); +static void reverse_buffer(guchar *buffer, gint length, gint bytes); +static void modal_message_box(gchar *text); + +GPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run /* run_proc */ +}; + + +/********************* + Globals + *******************/ + +/* This is needed to communicate the result from the dialog + box button's callback function*/ +gint dialog_result = -1; + +struct config_tag +{ + gint threshold; /* derivative comparison for edge detection */ + direction_t direction; /* of wind, LEFT or RIGHT */ + gint strength; /* how many pixels to bleed */ + algorithm_t alg; /* which algorithm */ + edge_t edge; /* controls abs, negation of derivative */ +}; + +typedef struct config_tag config_t; +config_t config = +{ + 10, /* threshold for derivative edge detection */ + LEFT, /* bleed to the right */ + 10, /* how many pixels to bleed */ + RENDER_WIND, /* default algorithm */ + LEADING /* abs(derivative); */ +}; + + + +MAIN() + +static void +query(void) +{ + static GParamDef args[] = + { + {PARAM_INT32, "run_mode", "Interactive, non-interactive"}, + {PARAM_IMAGE, "image", "Input image (unused)"}, + {PARAM_DRAWABLE, "drawable", "Input drawable"}, + {PARAM_INT32, "threshold", "Controls where blending will be done >= 0"}, + {PARAM_INT32, "direction", "Left or Right: 0 or 1"}, + {PARAM_INT32, "strength", "Controls the extent of the blending > 1"}, + {PARAM_INT32, "alg", "WIND, BLAST"}, + {PARAM_INT32, "edge", "LEADING, TRAILING, or BOTH"} + }; + static GParamDef *return_vals = NULL; + static int nargs = sizeof(args) / sizeof(args[0]); + static int nreturn_vals = 0; + + gimp_install_procedure("plug_in_wind", + "Renders a wind effect.", + "Renders a wind effect.", + "Nigel Wetten", + "Nigel Wetten", + "1998", + "/Filters/Distorts/Wind", + "RGB*", + PROC_PLUG_IN, + nargs, nreturn_vals, + args, return_vals); + return; +} /* query */ + +/*****/ + +static void +run(gchar *name, gint nparams, GParam *param, gint *nreturn_vals, + GParam **return_vals) +{ + static GParam values[1]; + GDrawable *drawable; + GRunModeType run_mode; + GStatusType status = STATUS_SUCCESS; + + run_mode = param[0].data.d_int32; + drawable = gimp_drawable_get(param[2].data.d_drawable); + gimp_tile_cache_ntiles(drawable->width / gimp_tile_width() + 1); + switch (run_mode) + { + case RUN_NONINTERACTIVE: + if (nparams == 8) + { + config.threshold = param[3].data.d_int32; + config.direction = param[4].data.d_int32; + config.strength = param[5].data.d_int32; + config.alg = param[6].data.d_int32; + config.edge = param[7].data.d_int32; + if (render_effect(drawable) == -1) + { + status = STATUS_EXECUTION_ERROR; + } + } + else + { + status = STATUS_CALLING_ERROR; + } + break; + + case RUN_INTERACTIVE: + gimp_get_data("plug_in_wind", &config); + dialog_box(); + if (dialog_result == -1) + { + status = STATUS_EXECUTION_ERROR; + break; + } + if (render_effect(drawable) == -1) + { + status = STATUS_CALLING_ERROR; + break; + } + gimp_set_data("plug_in_wind", &config, sizeof(config_t)); + gimp_displays_flush(); + break; + + case RUN_WITH_LAST_VALS: + gimp_get_data("plug_in_wind", &config); + if (render_effect(drawable) == -1) + { + status = STATUS_EXECUTION_ERROR; + gimp_message("An execution error occured."); + } + else + { + gimp_displays_flush(); + } + + } /* switch */ + + gimp_drawable_detach(drawable); + + *nreturn_vals = 1; + *return_vals = values; + values[0].type = PARAM_STATUS; + values[0].data.d_status = status; + + return; +} /* run */ + +/*****/ + +static gint +render_effect(GDrawable *drawable) +{ + if (config.alg == RENDER_WIND) + { + gimp_progress_init("Rendering Wind..."); + render_wind(drawable, config.threshold, config. strength, + config.direction, config.edge); + } + else if (config.alg == RENDER_BLAST) + { + gimp_progress_init("Rendering Blast..."); + render_blast(drawable, config.threshold, config.strength, + config.direction, config.edge); + } + return 0; +} /* render_effect */ + +/*****/ + +static void +render_blast(GDrawable *drawable, gint threshold, gint strength, + direction_t direction, edge_t edge) +{ + gint x1, x2, y1, y2; + gint width = drawable->width; + gint height = drawable->height; + gint bytes = drawable->bpp; + guchar *buffer; + GPixelRgn src_region, dest_region; + gint row; + gint row_stride = width * bytes; + gint marker = 0; + gint lpi = row_stride - bytes; + + gimp_drawable_mask_bounds(drawable->id, &x1, &y1, &x2, &y2); + gimp_pixel_rgn_init(&src_region, drawable, 0, 0, width, height, FALSE, FALSE); + gimp_pixel_rgn_init(&dest_region, drawable, 0, 0, width, height, TRUE, TRUE); + + buffer = malloc(row_stride); + + for (row = y1; row < y2; row++) + { + + gimp_pixel_rgn_get_row(&src_region, buffer, x1, row, width); + if (direction == RIGHT) + { + reverse_buffer(buffer, row_stride, bytes); + } + marker = render_blast_row(buffer, bytes, lpi, threshold, strength, edge); + if (direction == RIGHT) + { + reverse_buffer(buffer, row_stride, bytes); + } + gimp_pixel_rgn_set_row(&dest_region, buffer, x1, row, width); + gimp_progress_update((double) row / (double) (y2 - y1)); + if (marker) + { + gint j, limit; + + limit = 1 + rand() % 2; + for (j = 0; (j < limit) && (row < y2); j++) + { + row++; + if (row < y2) + { + gimp_pixel_rgn_get_row(&src_region, buffer, x1, row, width); + gimp_pixel_rgn_set_row(&dest_region, buffer, x1, row, width); + } + } + marker = 0; + } + } /* for */ + free(buffer); + + /* update the region */ + gimp_drawable_flush(drawable); + gimp_drawable_merge_shadow(drawable->id, TRUE); + gimp_drawable_update(drawable->id, x1, y1, x2 - x1, y2 - y1); + + return; +} /* render_blast */ + +/*****/ + +static void +render_wind(GDrawable *drawable, gint threshold, gint strength, + direction_t direction, edge_t edge) +{ + GPixelRgn src_region, dest_region; + gint width = drawable->width; + gint height = drawable->height; + gint bytes = drawable->bpp; + gint row_stride = width * bytes; + gint comp_stride = bytes * COMPARE_WIDTH; + gint row; + guchar *sb; + gint lpi = row_stride - comp_stride; + gint x1, y1, x2, y2; + + gimp_drawable_mask_bounds(drawable->id, &x1, &y1, &x2, &y2); + gimp_pixel_rgn_init(&src_region, drawable, 0, 0, width, height, + FALSE, FALSE); + gimp_pixel_rgn_init(&dest_region, drawable, 0, 0, width, height, TRUE, TRUE); + sb = g_malloc(row_stride); + + for (row = y1; row < y2; row++) + { + gimp_pixel_rgn_get_row(&src_region, sb, x1, row, width); + if (direction == RIGHT) + { + reverse_buffer(sb, row_stride, bytes); + } + render_wind_row(sb, bytes, lpi, threshold, strength, edge); + if (direction == RIGHT) + { + reverse_buffer(sb, row_stride, bytes); + } + gimp_pixel_rgn_set_row(&dest_region, sb, x1, row, width); + gimp_progress_update((gdouble) row / (gdouble) (y2 - y1)); + } + free(sb); + gimp_drawable_flush(drawable); + gimp_drawable_merge_shadow(drawable->id, TRUE); + gimp_drawable_update(drawable->id, x1, y1, x2 - x1, y2 - y1); + return; +} /* render_wind */ + +/*****/ + +static gint +render_blast_row(guchar *buffer, gint bytes, gint lpi, gint threshold, + gint strength, edge_t edge) +{ + gint Ri, Gi, Bi; + gint sbi, lbi; + gint bleed_length; + gint i, j; + gint weight, random_factor; + gint skip = 0; + + for (j = 0; j < lpi; j += bytes) + { + Ri = j; Gi = j + 1; Bi = j + 2; + + if (threshold_exceeded(buffer+Ri, buffer+Ri+bytes, edge, threshold)) + { + /* we have found an edge, do bleeding */ + sbi = Ri; + + weight = rand() % 10; + if (weight > 5) + { + random_factor = 2; + } + else if (weight > 3) + { + random_factor = 3; + } + else + { + random_factor = 4; + } + bleed_length = 0; + switch (rand() % random_factor) + { + case 3: + bleed_length += strength; + /* fall through to add up multiples of strength */ + case 2: + bleed_length += strength; + /* fall through */ + case 1: + bleed_length += strength; + /* fall through */ + case 0: + bleed_length += strength; + /* fall through */ + } + + lbi = sbi + bytes * bleed_length; + if (lbi > lpi) + { + lbi = lpi; + } + + for (i = sbi; i < lbi; i += bytes) + { + buffer[i] = buffer[Ri]; + buffer[i+1] = buffer[Gi]; + buffer[i+2] = buffer[Bi]; + } + j = lbi - bytes; + if ((rand() % 10) > 7) + { + skip = 1; + } + } /* if */ + } /* for j=0 */ + return skip; +} /* render_blast_row */ + +/*****/ + +static void +render_wind_row(guchar *sb, gint bytes, gint lpi, gint threshold, + gint strength, edge_t edge) +{ + gint i, j; + gint bleed_length; + gint blend_amt_R, blend_amt_G, blend_amt_B; + gint blend_colour_R, blend_colour_G, blend_colour_B; + gint target_colour_R, target_colour_G, target_colour_B; + gdouble bleed_length_max; + gint bleed_variation; + gint n; + gint sbi; /* starting bleed index */ + gint lbi; /* last bleed index */ + gdouble denominator; + gint comp_stride = bytes * COMPARE_WIDTH; + + for (j = 0; j < lpi; j += bytes) + { + gint Ri = j; + gint Gi = j + 1; + gint Bi = j + 2; + + if (threshold_exceeded(sb+Ri, sb+Ri+comp_stride, edge, threshold)) + { + /* we have found an edge, do bleeding */ + sbi = Ri + comp_stride; + blend_colour_R = sb[Ri]; + blend_colour_G = sb[Gi]; + blend_colour_B = sb[Bi]; + target_colour_R = sb[sbi]; + target_colour_G = sb[sbi+1]; + target_colour_B = sb[sbi+2]; + bleed_length_max = strength; + + if (rand() % 3) /* introduce weighted randomness */ + { + bleed_length_max = strength; + } + else + { + bleed_length_max = 4 * strength; + } + + bleed_variation = 1 + + (gint) (bleed_length_max * rand() / (RAND_MAX + 1.0)); + + lbi = sbi + bleed_variation * bytes; + if (lbi > lpi) + { + lbi = lpi; /* stop overunning the buffer */ + } + + bleed_length = bleed_variation; + + blend_amt_R = target_colour_R - blend_colour_R; + blend_amt_G = target_colour_G - blend_colour_G; + blend_amt_B = target_colour_B - blend_colour_B; + denominator = bleed_length * bleed_length + bleed_length; + denominator = 2.0 / denominator; + n = bleed_length; + for (i = sbi; i < lbi; i += bytes) + { + + /* check against original colour */ + if (!threshold_exceeded(sb+Ri, sb+i, edge, threshold) + && (rand() % 2)) + { + break; + } + + blend_colour_R += blend_amt_R * n * denominator; + blend_colour_G += blend_amt_G * n * denominator; + blend_colour_B += blend_amt_B * n * denominator; + + if (blend_colour_R > 255) blend_colour_R = 255; + else if (blend_colour_R < 0) blend_colour_R = 0; + if (blend_colour_G > 255) blend_colour_G = 255; + else if (blend_colour_G < 0) blend_colour_G = 0; + if (blend_colour_B > 255) blend_colour_B = 255; + else if (blend_colour_B < 0) blend_colour_B = 0; + + sb[i] = (blend_colour_R * 2 + sb[i]) / 3; + sb[i+1] = (blend_colour_G * 2 + sb[i+1]) / 3; + sb[i+2] = (blend_colour_B * 2 + sb[i+2]) / 3; + + if (threshold_exceeded(sb+i, sb+i+comp_stride, BOTH, + threshold)) + { + target_colour_R = sb[i+comp_stride]; + target_colour_G = sb[i+comp_stride+1]; + target_colour_B = sb[i+comp_stride+2]; + blend_amt_R = target_colour_R - blend_colour_R; + blend_amt_G = target_colour_G - blend_colour_G; + blend_amt_B = target_colour_B - blend_colour_B; + denominator = n * n + n; + denominator = 2.0 / denominator; + } + n--; + } + } /* if */ + } /* for j=0 */ + return; +} /* render_wind_row */ + +/*****/ + +static gint +threshold_exceeded(guchar *pixel_R1, guchar *pixel_R2, edge_t edge, + gint threshold) +{ + gint derivative_R, derivative_G, derivative_B; + gint return_value; + + get_derivative(pixel_R1, pixel_R2, edge, + &derivative_R, &derivative_G, &derivative_B); + + if(((derivative_R + derivative_G + derivative_B) / 3) > threshold) + { + return_value = 1; + } + else + { + return_value = 0; + } + return return_value; +} /* threshold_exceeded */ + +/*****/ + +static void +get_derivative(guchar *pixel_R1, guchar *pixel_R2, edge_t edge, + gint *derivative_R, gint *derivative_G, gint *derivative_B) +{ + guchar *pixel_G1 = pixel_R1 + 1; + guchar *pixel_B1 = pixel_R1 + 2; + guchar *pixel_G2 = pixel_R2 + 1; + guchar *pixel_B2 = pixel_R2 + 2; + + *derivative_R = *pixel_R2 - *pixel_R1; + *derivative_G = *pixel_G2 - *pixel_G1; + *derivative_B = *pixel_B2 - *pixel_B1; + + if (edge == BOTH) + { + *derivative_R = abs(*derivative_R); + *derivative_G = abs(*derivative_G); + *derivative_B = abs(*derivative_B); + } + else if (edge == LEADING) + { + *derivative_R = -(*derivative_R); + *derivative_G = -(*derivative_G); + *derivative_B = -(*derivative_B); + } + else if (edge == TRAILING) + { + /* no change needed */ + } + return; +} /* get_derivative */ + +/*****/ + +static void +reverse_buffer(guchar *buffer, gint length, gint bytes) +{ + gint i, si; + gint temp; + gint midpoint; + + midpoint = length / 2; + for (i = 0; i < midpoint; i += bytes) + { + si = length - bytes - i; + + temp = buffer[i]; + buffer[i] = buffer[si]; + buffer[si] = (guchar) temp; + + temp = buffer[i+1]; + buffer[i+1] = buffer[si+1]; + buffer[si+1] = (guchar) temp; + + temp = buffer[i+2]; + buffer[i+2] = buffer[si+2]; + buffer[si+2] = (guchar) temp; + } + + return; +} /* reverse_buffer */ + +/*****/ + +/*************************************************** + GUI + ***************************************************/ + +static void +msg_ok_callback(GtkWidget *widget, gpointer data) +{ + gtk_grab_remove(GTK_WIDGET(data)); + gtk_widget_destroy(GTK_WIDGET(data)); + return; +} /* msg_ok_callback */ + +/*****/ + +static void +msg_close_callback(GtkWidget *widget, gpointer data) +{ + return; +} /* msg_close_callback */ + +/*****/ + +static void +modal_message_box(gchar *text) +{ + GtkWidget *message_box; + GtkWidget *button; + GtkWidget *label; + + message_box = gtk_dialog_new(); + gtk_window_set_title(GTK_WINDOW(message_box), "Ooops!"); + gtk_window_position(GTK_WINDOW(message_box), GTK_WIN_POS_MOUSE); + gtk_signal_connect(GTK_OBJECT(message_box), "destroy", + (GtkSignalFunc) msg_close_callback, NULL); + + label = gtk_label_new(text); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(message_box)->vbox), label, + TRUE, TRUE, 0); + gtk_widget_show(label); + + button = gtk_button_new_with_label("OK"); + GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); + gtk_signal_connect(GTK_OBJECT(button), "clicked", + (GtkSignalFunc) msg_ok_callback, message_box); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(message_box)->action_area), + button, TRUE, TRUE, 0); + gtk_widget_grab_default(button); + gtk_widget_show(button); + + gtk_grab_add(message_box); + gtk_widget_show(message_box); + return; +} /* modal_message_box */ + +/*****/ + +static void +close_callback(GtkWidget *widget, gpointer data) +{ + gtk_main_quit(); + return; +} /* close_callback */ + +/*****/ + +static void +ok_callback(GtkWidget *widget, gpointer data) +{ + /* we have to stop the dialog from being closed with strength < 1 */ + + if (config.strength < 1) + { + modal_message_box(NEGATIVE_STRENGTH_TEXT); + } + else + { + dialog_result = 1; + gtk_widget_destroy(GTK_WIDGET(data)); + } + return; +} /* ok_callback */ + +/*****/ + +static void +entry_callback(GtkWidget *widget, gpointer data) +{ + GtkAdjustment *adjustment; + gint new_value; + + new_value = atoi(gtk_entry_get_text(GTK_ENTRY(widget))); + + if (*(gint *) data != new_value) + { + *(gint *) data = new_value; + adjustment = gtk_object_get_user_data(GTK_OBJECT(widget)); + if ((new_value >= adjustment-> lower) + && (new_value <= adjustment->upper)) + { + adjustment->value = new_value; + gtk_signal_handler_block_by_data(GTK_OBJECT(adjustment), data); + gtk_signal_emit_by_name(GTK_OBJECT(adjustment), "value_changed"); + gtk_signal_handler_unblock_by_data(GTK_OBJECT(adjustment), data); + } + } + return; +} /* entry_callback */ + +/*****/ + +static void +radio_button_alg_callback(GtkWidget *widget, gpointer data) +{ + if (GTK_TOGGLE_BUTTON (widget)->active) /* button is TRUE, i.e. checked */ + { + config.alg = (algorithm_t) data; + } + return; +} /* radio_button_alg_callback */ + +/*****/ + +static void +radio_button_direction_callback(GtkWidget *widget, gpointer data) +{ + if (GTK_TOGGLE_BUTTON (widget)->active) /* button is checked */ + { + config.direction = (direction_t) data; + } + return; +} /* radio_button_direction_callback */ + +/*****/ + +static void +radio_button_edge_callback(GtkWidget *widget, gpointer data) +{ + if (GTK_TOGGLE_BUTTON (widget)->active) /* button is selected */ + { + config.edge = (edge_t) data; + } + return; +} /* radio_button_edge_callback */ + +/*****/ + +static void +adjustment_callback(GtkAdjustment *adjustment, gpointer data) +{ + GtkWidget *entry; + gchar buffer[50]; + + if (*(gint *)data != adjustment->value) + { + *(gint *) data = adjustment->value; + entry = gtk_object_get_user_data(GTK_OBJECT(adjustment)); + sprintf(buffer, "%d", *(gint *) data); + gtk_signal_handler_block_by_data(GTK_OBJECT(entry), data); + gtk_entry_set_text(GTK_ENTRY(entry), buffer); + gtk_signal_handler_unblock_by_data(GTK_OBJECT(entry), data); + } + return; +} /* adjustment_callback */ + +/*****/ + +static void +dialog_box(void) +{ + GtkWidget *table; + GtkWidget *outer_table; + GtkObject *adjustment; + GtkWidget *scale; + GtkWidget *rbutton; + GSList *list; + gchar *text_label; + GtkWidget *frame; + GtkWidget *outer_frame; + GtkWidget *dlg; + GtkWidget *button; + GtkWidget *label; + GtkWidget *entry; + gchar buffer[12]; + gchar **argv; + gint argc; + GtkTooltips *tooltips; + GdkColor tips_fg, tips_bg; + + argc = 1; + argv = g_new(gchar *, 1); + argv[0] = g_strdup(PLUG_IN_NAME); + + gtk_init(&argc, &argv); + gtk_rc_parse(gimp_gtkrc()); + + dlg = gtk_dialog_new(); + gtk_window_set_title(GTK_WINDOW(dlg), PLUG_IN_NAME); + gtk_window_position(GTK_WINDOW(dlg), GTK_WIN_POS_MOUSE); + gtk_signal_connect(GTK_OBJECT(dlg), + "destroy", (GtkSignalFunc) close_callback, NULL); + + /* Action area */ + button = gtk_button_new_with_label("OK"); + GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); + gtk_signal_connect(GTK_OBJECT(button), "clicked", + (GtkSignalFunc) ok_callback, dlg); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->action_area), button, + TRUE, TRUE, 0); + gtk_widget_grab_default(button); + gtk_widget_show(button); + + button = gtk_button_new_with_label("Cancel"); + GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT); + gtk_signal_connect_object(GTK_OBJECT(button), "clicked", + (GtkSignalFunc) gtk_widget_destroy, + GTK_OBJECT(dlg)); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->action_area), button, + TRUE, TRUE, 0); + gtk_widget_show(button); + + /* init tooltips */ + tooltips = gtk_tooltips_new(); + tips_fg.red = 0; + tips_fg.green = 0; + tips_fg.blue = 0; + gdk_color_alloc(gtk_widget_get_colormap(dlg), &tips_fg); + tips_bg.red = 61669; + tips_bg.green = 59113; + tips_bg.blue = 35979; + gdk_color_alloc(gtk_widget_get_colormap(dlg), &tips_bg); + gtk_tooltips_set_colors(tooltips, &tips_bg, &tips_fg); + + /**************************************************** + frame for sliders + ****************************************************/ + frame = gtk_frame_new(NULL); + gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN); + gtk_container_border_width(GTK_CONTAINER(frame), 10); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->vbox), frame, TRUE, TRUE, 0); + + table = gtk_table_new(3, 2, FALSE); + gtk_container_border_width(GTK_CONTAINER(table), 10); + gtk_container_add(GTK_CONTAINER(frame), table); + + /***************************************************** + slider and entry for threshold + ***************************************************/ + + label = gtk_label_new("Threshold:"); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0); + gtk_widget_show(label); + + adjustment = gtk_adjustment_new(config.threshold, MIN_THRESHOLD, + MAX_THRESHOLD, 1.0, 1.0, 0); + gtk_signal_connect(GTK_OBJECT(adjustment), "value_changed", + (GtkSignalFunc) adjustment_callback, + &config.threshold); + scale = gtk_hscale_new(GTK_ADJUSTMENT(adjustment)); + gtk_widget_set_usize(scale, SCALE_WIDTH, 0); + gtk_table_attach(GTK_TABLE(table), scale, 1, 2, 0, 1, GTK_FILL, 0, 0, 0); + gtk_scale_set_draw_value(GTK_SCALE(scale), FALSE); + gtk_range_set_update_policy(GTK_RANGE(scale), GTK_UPDATE_CONTINUOUS); + gtk_widget_show(scale); + gtk_tooltips_set_tip(tooltips, scale, THRESHOLD_TEXT, NULL); + + entry = gtk_entry_new(); + gtk_object_set_user_data(GTK_OBJECT(entry), adjustment); + gtk_object_set_user_data(GTK_OBJECT(adjustment), entry); + gtk_widget_set_usize(entry, ENTRY_WIDTH, 0); + sprintf(buffer, "%i", config.threshold); + gtk_entry_set_text(GTK_ENTRY(entry), buffer); + gtk_signal_connect(GTK_OBJECT(entry), "changed", + GTK_SIGNAL_FUNC(entry_callback), + (gpointer) &config.threshold); + gtk_table_attach(GTK_TABLE(table), entry, 2, 3, 0, 1, GTK_FILL, 0, 0, 0); + gtk_widget_show(entry); + gtk_tooltips_set_tip(tooltips, entry, THRESHOLD_TEXT, NULL); + + /***************************************************** + slider and entry for strength of wind + ****************************************************/ + + label = gtk_label_new("Strength:"); + gtk_misc_set_alignment(GTK_MISC(label), 0, 0); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, GTK_FILL, 0, 0, 0); + gtk_widget_show(label); + + adjustment = gtk_adjustment_new(config.strength, MIN_STRENGTH, + MAX_STRENGTH, 1.0, 1.0, 0); + gtk_signal_connect(GTK_OBJECT(adjustment), "value_changed", + (GtkSignalFunc) adjustment_callback, + &config.strength); + scale = gtk_hscale_new(GTK_ADJUSTMENT(adjustment)); + gtk_widget_set_usize(scale, SCALE_WIDTH, 0); + gtk_table_attach(GTK_TABLE(table), scale, 1, 2, 1, 2, GTK_FILL, 0, 0, 0); + gtk_scale_set_draw_value(GTK_SCALE(scale), FALSE); + gtk_range_set_update_policy(GTK_RANGE(scale), GTK_UPDATE_CONTINUOUS); + gtk_widget_show(scale); + gtk_tooltips_set_tip(tooltips, scale, STRENGTH_TEXT, NULL); + + entry = gtk_entry_new(); + gtk_object_set_user_data(GTK_OBJECT(entry), adjustment); + gtk_object_set_user_data(GTK_OBJECT(adjustment), entry); + gtk_widget_set_usize(entry, ENTRY_WIDTH, 0); + sprintf(buffer, "%i", config.strength); + gtk_entry_set_text(GTK_ENTRY(entry), buffer); + gtk_signal_connect(GTK_OBJECT(entry), "changed", + GTK_SIGNAL_FUNC(entry_callback), + (gpointer) &config.strength); + gtk_table_attach(GTK_TABLE(table), entry, 2, 3, 1, 2, GTK_FILL, 0, 0, 0); + gtk_widget_show(entry); + gtk_tooltips_set_tip(tooltips, entry, STRENGTH_TEXT, NULL); + + gtk_widget_show(table); + gtk_widget_show(frame); + + /***************************************************** + outer frame and table + ***************************************************/ + + outer_frame = gtk_frame_new(NULL); + gtk_frame_set_shadow_type(GTK_FRAME(outer_frame), GTK_SHADOW_NONE); + gtk_container_border_width(GTK_CONTAINER(outer_frame), 10); + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->vbox), outer_frame, + TRUE, TRUE, 0); + + outer_table = gtk_table_new(3, 3, FALSE); + gtk_container_border_width(GTK_CONTAINER(outer_table), 0); + gtk_table_set_col_spacings(GTK_TABLE(outer_table), 10); + gtk_container_add(GTK_CONTAINER(outer_frame), outer_table); + + /********************************************************* + radio buttons for choosing wind rendering algorithm + ******************************************************/ + + frame = gtk_frame_new("Style"); + gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN); + gtk_container_border_width(GTK_CONTAINER(frame), 0); + gtk_table_attach(GTK_TABLE(outer_table), frame, 0, 1, 0, 3, + GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0); + + table = gtk_table_new(3, 2, FALSE); + gtk_container_border_width(GTK_CONTAINER(table), 10); + gtk_container_add(GTK_CONTAINER(frame), table); + + text_label = "Wind"; + rbutton = gtk_radio_button_new_with_label(NULL, text_label); + list = gtk_radio_button_group((GtkRadioButton *) rbutton); + gtk_toggle_button_set_state((GtkToggleButton *) rbutton, + config.alg == RENDER_WIND ? TRUE : FALSE); + gtk_signal_connect(GTK_OBJECT(rbutton), "toggled", + GTK_SIGNAL_FUNC (radio_button_alg_callback), + (gpointer) RENDER_WIND); + gtk_table_attach(GTK_TABLE(table), rbutton, 0, 1, 0, 1, GTK_FILL, 0, 0, 0); + gtk_widget_show(rbutton); + gtk_tooltips_set_tip(tooltips, rbutton, WIND_TEXT, NULL); + + text_label = "Blast"; + rbutton = gtk_radio_button_new_with_label(list, text_label); + list = gtk_radio_button_group((GtkRadioButton *) rbutton); + gtk_toggle_button_set_state((GtkToggleButton *) rbutton, + config.alg == RENDER_BLAST ? TRUE : FALSE); + gtk_signal_connect(GTK_OBJECT(rbutton), "toggled", + GTK_SIGNAL_FUNC (radio_button_alg_callback), + (gpointer) RENDER_BLAST); + gtk_table_attach(GTK_TABLE(table), rbutton, 0, 1, 1, 2, GTK_FILL, 0, 0, 0); + gtk_widget_show(rbutton); + gtk_tooltips_set_tip(tooltips, rbutton, BLAST_TEXT, NULL); + + gtk_widget_show(table); + gtk_widget_show(frame); + + /****************************************************** + radio buttons for choosing LEFT or RIGHT + **************************************************/ + frame = gtk_frame_new("Direction"); + gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN); + gtk_container_border_width(GTK_CONTAINER(frame), 0); + gtk_table_attach(GTK_TABLE(outer_table), frame, 1, 2, 0, 3, + GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0); + + table = gtk_table_new(1, 2, FALSE); + gtk_container_border_width(GTK_CONTAINER(table), 10); + gtk_container_add(GTK_CONTAINER(frame), table); + + rbutton = gtk_radio_button_new_with_label(NULL, "Left"); + list = gtk_radio_button_group((GtkRadioButton *) rbutton); + gtk_toggle_button_set_state((GtkToggleButton *) rbutton, + config.direction == LEFT ? TRUE : FALSE); + gtk_signal_connect(GTK_OBJECT(rbutton), "toggled", + GTK_SIGNAL_FUNC(radio_button_direction_callback), + (gpointer) LEFT); + gtk_table_attach(GTK_TABLE(table), rbutton, 0, 1, 0, 1, GTK_FILL, 0, 0, 0); + gtk_widget_show(rbutton); + gtk_tooltips_set_tip(tooltips, rbutton, LEFT_TEXT, NULL); + + rbutton = gtk_radio_button_new_with_label(list, "Right"); + gtk_toggle_button_set_state((GtkToggleButton *) rbutton, + config.direction == RIGHT ? TRUE : FALSE); + gtk_signal_connect(GTK_OBJECT(rbutton), "toggled", + GTK_SIGNAL_FUNC(radio_button_direction_callback), + (gpointer) RIGHT); + gtk_table_attach(GTK_TABLE(table), rbutton, 0, 1, 1, 2, GTK_FILL, 0, 0, 0); + gtk_widget_show(rbutton); + gtk_tooltips_set_tip(tooltips, rbutton, RIGHT_TEXT, NULL); + + gtk_widget_show(table); + gtk_widget_show(frame); + + /***************************************************** + radio buttons for choosing BOTH, LEADING, TRAILING + ***************************************************/ + + frame = gtk_frame_new("Edge affected"); + gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN); + gtk_container_border_width(GTK_CONTAINER(frame), 0); + gtk_table_attach(GTK_TABLE(outer_table), frame, 2, 3, 0, 3, + GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0); + + table = gtk_table_new(1, 3, FALSE); + gtk_container_border_width(GTK_CONTAINER(table), 10); + gtk_container_add(GTK_CONTAINER(frame), table); + + rbutton = gtk_radio_button_new_with_label(NULL, "Leading"); + list = gtk_radio_button_group((GtkRadioButton *) rbutton); + gtk_toggle_button_set_state((GtkToggleButton *) rbutton, + config.edge == LEADING ? TRUE : FALSE); + gtk_signal_connect(GTK_OBJECT(rbutton), "toggled", + GTK_SIGNAL_FUNC(radio_button_edge_callback), + (gpointer) LEADING); + gtk_table_attach(GTK_TABLE(table), rbutton, 0, 1, 0, 1, GTK_FILL, 0, 0, 0); + gtk_widget_show(rbutton); + gtk_tooltips_set_tip(tooltips, rbutton, LEADING_TEXT, NULL); + + rbutton = gtk_radio_button_new_with_label(list, "Trailing"); + list = gtk_radio_button_group((GtkRadioButton *) rbutton); + gtk_toggle_button_set_state((GtkToggleButton *) rbutton, + config.edge == TRAILING ? TRUE : FALSE); + gtk_signal_connect(GTK_OBJECT(rbutton), "toggled", + GTK_SIGNAL_FUNC(radio_button_edge_callback), + (gpointer) TRAILING); + gtk_table_attach(GTK_TABLE(table), rbutton, 0, 1, 1, 2, GTK_FILL, 0, 0, 0); + gtk_widget_show(rbutton); + gtk_tooltips_set_tip(tooltips, rbutton, TRAILING_TEXT, NULL); + + rbutton = gtk_radio_button_new_with_label(list, "Both"); + gtk_toggle_button_set_state((GtkToggleButton *) rbutton, + config.edge == BOTH ? TRUE : FALSE); + gtk_signal_connect(GTK_OBJECT(rbutton), "toggled", + GTK_SIGNAL_FUNC(radio_button_edge_callback), + (gpointer) BOTH); + gtk_table_attach(GTK_TABLE(table), rbutton, 0, 1, 2, 3, GTK_FILL, 0, 0, 0); + gtk_widget_show(rbutton); + gtk_tooltips_set_tip(tooltips, rbutton, BOTH_TEXT, NULL); + + gtk_widget_show(table); + gtk_widget_show(frame); + + gtk_widget_show(outer_table); + gtk_widget_show(outer_frame); + gtk_widget_show(dlg); + + gtk_main(); + gdk_flush(); + + return; +} + +/*****/