/* The GIMP -- an image manipulation program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * 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. */ /* Mosaic is a filter which transforms an image into * what appears to be a mosaic, composed of small primitives, * each of constant color and of an approximate size. * Copyright (C) 1996 Spencer Kimball * Speedups by Elliot Lee */ #include "config.h" #include #include #include #include #include #include "libgimp/stdplugins-intl.h" #define SCALE_WIDTH 150 #define HORIZONTAL 0 #define VERTICAL 1 #define SUPERSAMPLE 3 #define MAG_THRESHOLD 7.5 #define COUNT_THRESHOLD 0.1 #define MAX_POINTS 12 #define SQUARES 0 #define HEXAGONS 1 #define OCTAGONS 2 #define SMOOTH 0 #define ROUGH 1 #define BW 0 #define FG_BG 1 typedef struct { gdouble x, y; } Vertex; typedef struct { gint npts; Vertex pts[MAX_POINTS]; } Polygon; typedef struct { gdouble base_x, base_y; gdouble norm_x, norm_y; gdouble light; } SpecVec; typedef struct { gdouble tile_size; gdouble tile_height; gdouble tile_spacing; gdouble tile_neatness; gboolean tile_allow_split; gdouble light_dir; gdouble color_variation; gboolean antialiasing; gint color_averaging; gint tile_type; gint tile_surface; gint grout_color; } MosaicVals; /* Declare local functions. */ static void query (void); static void run (const gchar *name, gint nparams, const GimpParam *param, gint *nreturn_vals, GimpParam **return_vals); static void mosaic (GimpDrawable *drawable); /* user interface functions */ static gint mosaic_dialog (void); /* gradient finding machinery */ static void find_gradients (GimpDrawable *drawable, gdouble std_dev); static void find_max_gradient (GimpPixelRgn *src_rgn, GimpPixelRgn *dest_rgn); /* gaussian & 1st derivative */ static void gaussian_deriv (GimpPixelRgn *src_rgn, GimpPixelRgn *dest_rgn, gint direction, gdouble std_dev, gint * prog, gint max_prog, gint ith_prog); static void make_curve (gint * curve, gint * sum, gdouble std_dev, gint length); static void make_curve_d (gint * curve, gint * sum, gdouble std_dev, gint length); /* grid creation and localization machinery */ static gdouble fp_rand (gdouble val); static void grid_create_squares (gint x1, gint y1, gint x2, gint y2); static void grid_create_hexagons (gint x1, gint y1, gint x2, gint y2); static void grid_create_octagons (gint x1, gint y1, gint x2, gint y2); static void grid_localize (gint x1, gint y1, gint x2, gint y2); static void grid_render (GimpDrawable * drawable); static void split_poly (Polygon * poly, GimpDrawable * drawable, guchar * col, gdouble * dir, gdouble color_vary); static void clip_poly (gdouble * vec, gdouble * pt, Polygon * poly, Polygon * new_poly); static void clip_point (gdouble * dir, gdouble * pt, gdouble x1, gdouble y1, gdouble x2, gdouble y2, Polygon * poly); static void process_poly (Polygon * poly, gint allow_split, GimpDrawable * drawable, guchar * col, gint vary); static void render_poly (Polygon * poly, GimpDrawable * drawable, guchar * col, gdouble vary); static void find_poly_dir (Polygon * poly, guchar * m_gr, guchar * h_gr, guchar * v_gr, gdouble * dir, gdouble * loc, gint x1, gint y1, gint x2, gint y2); static void find_poly_color (Polygon * poly, GimpDrawable * drawable, guchar * col, double vary); static void scale_poly (Polygon * poly, gdouble cx, gdouble cy, gdouble scale); static void fill_poly_color (Polygon * poly, GimpDrawable * drawable, guchar * col); static void fill_poly_image (Polygon * poly, GimpDrawable * drawable, gdouble vary); static void calc_spec_vec (SpecVec * vec, gint xs, gint ys, gint xe, gint ye); static gdouble calc_spec_contrib (SpecVec * vec, gint n, gdouble x, gdouble y); static void convert_segment (gint x1, gint y1, gint x2, gint y2, gint offset, gint * min, gint * max); static void polygon_add_point (Polygon * poly, gdouble x, gdouble y); static gint polygon_find_center (Polygon * poly, gdouble * x, gdouble * y); static void polygon_translate (Polygon * poly, gdouble tx, gdouble ty); static void polygon_scale (Polygon * poly, gdouble scale); static gint polygon_extents (Polygon * poly, gdouble * min_x, gdouble * min_y, gdouble * max_x, gdouble * max_y); static void polygon_reset (Polygon * poly); /* * Some static variables */ static gdouble std_dev = 1.0; static gdouble light_x; static gdouble light_y; static gdouble scale; static guchar *h_grad; static guchar *v_grad; static guchar *m_grad; static Vertex *grid; static gint grid_rows; static gint grid_cols; static gint grid_row_pad; static gint grid_col_pad; static gint grid_multiple; static gint grid_rowstride; static guchar back[4]; static guchar fore[4]; static SpecVec vecs[MAX_POINTS]; static MosaicVals mvals = { 15.0, /* tile_size */ 4.0, /* tile_height */ 1.0, /* tile_spacing */ 0.65, /* tile_neatness */ TRUE, /* tile_allow_split */ 135, /* light_dir */ 0.2, /* color_variation */ TRUE, /* antialiasing */ 1, /* color_averaging */ HEXAGONS, /* tile_type */ SMOOTH, /* tile_surface */ BW /* grout_color */ }; GimpPlugInInfo PLUG_IN_INFO = { NULL, /* init_proc */ NULL, /* quit_proc */ query, /* query_proc */ run, /* run_proc */ }; MAIN () static void query (void) { static GimpParamDef args[] = { { GIMP_PDB_INT32, "run_mode", "Interactive, non-interactive" }, { GIMP_PDB_IMAGE, "image", "Input image" }, { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" }, { GIMP_PDB_FLOAT, "tile_size", "Average diameter of each tile (in pixels)" }, { GIMP_PDB_FLOAT, "tile_height", "Apparent height of each tile (in pixels)" }, { GIMP_PDB_FLOAT, "tile_spacing", "Inter-tile spacing (in pixels)" }, { GIMP_PDB_FLOAT, "tile_neatness", "Deviation from perfectly formed tiles (0.0 - 1.0)" }, { GIMP_PDB_INT32, "tile_allow_split", "Allows splitting tiles at hard edges" }, { GIMP_PDB_FLOAT, "light_dir", "Direction of light-source (in degrees)" }, { GIMP_PDB_FLOAT, "color_variation", "Magnitude of random color variations (0.0 - 1.0)" }, { GIMP_PDB_INT32, "antialiasing", "Enables smoother tile output at the cost of speed" }, { GIMP_PDB_INT32, "color_averaging", "Tile color based on average of subsumed pixels" }, { GIMP_PDB_INT32, "tile_type", "Tile geometry: { SQUARES (0), HEXAGONS (1), OCTAGONS (2) }" }, { GIMP_PDB_INT32, "tile_surface", "Surface characteristics: { SMOOTH (0), ROUGH (1) }" }, { GIMP_PDB_INT32, "grout_color", "Grout color (black/white or fore/background): { BW (0), FG_BG (1) }" } }; static GimpParamDef *return_vals = NULL; static int nreturn_vals = 0; gimp_install_procedure ("plug_in_mosaic", "Convert the input drawable into a collection of tiles", "Help not yet written for this plug-in", "Spencer Kimball", "Spencer Kimball & Peter Mattis", "1996", N_("_Mosaic..."), "RGB*, GRAY*", GIMP_PLUGIN, G_N_ELEMENTS (args), nreturn_vals, args, return_vals); gimp_plugin_menu_register ("plug_in_mosaic", "/Filters/Distorts"); } static void run (const gchar *name, gint nparams, const GimpParam *param, gint *nreturn_vals, GimpParam **return_vals) { static GimpParam values[1]; GimpRunMode run_mode; GimpPDBStatusType status = GIMP_PDB_SUCCESS; GimpDrawable *active_drawable; run_mode = param[0].data.d_int32; INIT_I18N (); *nreturn_vals = 1; *return_vals = values; values[0].type = GIMP_PDB_STATUS; values[0].data.d_status = status; switch (run_mode) { case GIMP_RUN_INTERACTIVE: /* Possibly retrieve data */ gimp_get_data ("plug_in_mosaic", &mvals); /* First acquire information with a dialog */ if (! mosaic_dialog ()) return; break; case GIMP_RUN_NONINTERACTIVE: /* Make sure all the arguments are there! */ if (nparams != 15) status = GIMP_PDB_CALLING_ERROR; if (status == GIMP_PDB_SUCCESS) { mvals.tile_size = param[3].data.d_float; mvals.tile_height = param[4].data.d_float; mvals.tile_spacing = param[5].data.d_float; mvals.tile_neatness = param[6].data.d_float; mvals.tile_allow_split = (param[7].data.d_int32) ? TRUE : FALSE; mvals.light_dir = param[8].data.d_float; mvals.color_variation = param[9].data.d_float; mvals.antialiasing = (param[10].data.d_int32) ? TRUE : FALSE; mvals.color_averaging = (param[11].data.d_int32) ? TRUE : FALSE; mvals.tile_type = param[12].data.d_int32; mvals.tile_surface = param[13].data.d_int32; mvals.grout_color = param[14].data.d_int32; } if (status == GIMP_PDB_SUCCESS && (mvals.tile_type < SQUARES || mvals.tile_type > OCTAGONS)) status = GIMP_PDB_CALLING_ERROR; if (status == GIMP_PDB_SUCCESS && (mvals.tile_surface < SMOOTH || mvals.tile_surface > ROUGH)) status = GIMP_PDB_CALLING_ERROR; if (status == GIMP_PDB_SUCCESS && (mvals.grout_color < BW || mvals.grout_color > FG_BG)) status = GIMP_PDB_CALLING_ERROR; break; case GIMP_RUN_WITH_LAST_VALS: /* Possibly retrieve data */ gimp_get_data ("plug_in_mosaic", &mvals); break; default: break; } /* Get the active drawable */ active_drawable = gimp_drawable_get (param[2].data.d_drawable); /* Create the mosaic */ if ((status == GIMP_PDB_SUCCESS) && (gimp_drawable_is_rgb (active_drawable->drawable_id) || gimp_drawable_is_gray (active_drawable->drawable_id))) { /* set the tile cache size so that the gaussian blur works well */ gimp_tile_cache_ntiles (2 * (MAX (active_drawable->width, active_drawable->height) / gimp_tile_width () + 1)); /* run the effect */ mosaic (active_drawable); /* If the run mode is interactive, flush the displays */ if (run_mode != GIMP_RUN_NONINTERACTIVE) gimp_displays_flush (); /* Store mvals data */ if (run_mode == GIMP_RUN_INTERACTIVE) gimp_set_data ("plug_in_mosaic", &mvals, sizeof (MosaicVals)); } else if (status == GIMP_PDB_SUCCESS) { /* gimp_message ("mosaic: cannot operate on indexed color images"); */ status = GIMP_PDB_EXECUTION_ERROR; } values[0].data.d_status = status; gimp_drawable_detach (active_drawable); } static void mosaic (GimpDrawable *drawable) { gint x1, y1, x2, y2; gint alpha; GimpRGB color; /* Find the mask bounds */ gimp_drawable_mask_bounds (drawable->drawable_id, &x1, &y1, &x2, &y2); /* progress bar for gradient finding */ gimp_progress_init (_("Finding Edges...")); /* Find the gradients */ find_gradients (drawable, std_dev); /* Create the tile geometry grid */ switch (mvals.tile_type) { case SQUARES: grid_create_squares (x1, y1, x2, y2); break; case HEXAGONS: grid_create_hexagons (x1, y1, x2, y2); break; case OCTAGONS: grid_create_octagons (x1, y1, x2, y2); break; default: break; } /* Deform the tiles based on image content */ grid_localize (x1, y1, x2, y2); switch (mvals.grout_color) { case BW: fore[0] = fore[1] = fore[2] = 255; back[0] = back[1] = back[2] = 0; break; case FG_BG: gimp_context_get_foreground (&color); gimp_drawable_get_color_uchar (drawable->drawable_id, &color, fore); gimp_context_get_background (&color); gimp_drawable_get_color_uchar (drawable->drawable_id, &color, back); break; } alpha = drawable->bpp - 1; light_x = -cos (mvals.light_dir * G_PI / 180.0); light_y = sin (mvals.light_dir * G_PI / 180.0); scale = (mvals.tile_spacing > mvals.tile_size / 2.0) ? 0.5 : 1.0 - mvals.tile_spacing / mvals.tile_size; /* Progress bar for rendering tiles */ gimp_progress_init (_("Rendering Tiles...")); /* Render the tiles */ grid_render (drawable); /* merge the shadow, update the drawable */ gimp_drawable_flush (drawable); gimp_drawable_merge_shadow (drawable->drawable_id, TRUE); gimp_drawable_update (drawable->drawable_id, x1, y1, (x2 - x1), (y2 - y1)); } static gint mosaic_dialog (void) { GtkWidget *dlg; GtkWidget *toggle; GtkWidget *vbox; GtkWidget *toggle_vbox; GtkWidget *main_hbox; GtkWidget *frame; GtkWidget *table; GtkObject *scale_data; gboolean run; gimp_ui_init ("mosaic", TRUE); dlg = gimp_dialog_new (_("Mosaic"), "mosaic", NULL, 0, gimp_standard_help_func, "plug-in-mosaic", GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); /* The main hbox -- splits the scripts and the info vbox */ main_hbox = gtk_hbox_new (FALSE, 12); gtk_container_set_border_width (GTK_CONTAINER (main_hbox), 12); gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), main_hbox, TRUE, TRUE, 0); /* The vbox for first column of options */ vbox = gtk_vbox_new (FALSE, 12); gtk_box_pack_start (GTK_BOX (main_hbox), vbox, FALSE, FALSE, 0); /* the vertical box and its toggle buttons */ frame = gimp_frame_new (_("Options")); gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); toggle_vbox = gtk_vbox_new (FALSE, 6); gtk_container_add (GTK_CONTAINER (frame), toggle_vbox); toggle = gtk_check_button_new_with_mnemonic (_("_Antialiasing")); gtk_box_pack_start (GTK_BOX (toggle_vbox), toggle, FALSE, FALSE, 0); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), mvals.antialiasing); gtk_widget_show (toggle); g_signal_connect (toggle, "toggled", G_CALLBACK (gimp_toggle_button_update), &mvals.antialiasing); toggle = gtk_check_button_new_with_mnemonic ( _("Co_lor averaging")); gtk_box_pack_start (GTK_BOX (toggle_vbox), toggle, FALSE, FALSE, 0); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), mvals.color_averaging); gtk_widget_show (toggle); g_signal_connect (toggle, "toggled", G_CALLBACK (gimp_toggle_button_update), &mvals.color_averaging); toggle = gtk_check_button_new_with_mnemonic ( _("Allo_w tile splitting")); gtk_box_pack_start (GTK_BOX (toggle_vbox), toggle, FALSE, FALSE, 0); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), mvals.tile_allow_split); gtk_widget_show (toggle); g_signal_connect (toggle, "toggled", G_CALLBACK (gimp_toggle_button_update), &mvals.tile_allow_split); toggle = gtk_check_button_new_with_mnemonic ( _("_Pitted surfaces")); gtk_box_pack_start (GTK_BOX (toggle_vbox), toggle, FALSE, FALSE, 0); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), (mvals.tile_surface == ROUGH)); gtk_widget_show (toggle); g_signal_connect (toggle, "toggled", G_CALLBACK (gimp_toggle_button_update), &mvals.tile_surface); toggle = gtk_check_button_new_with_mnemonic ( _("_FG/BG lighting")); gtk_box_pack_start (GTK_BOX (toggle_vbox), toggle, FALSE, FALSE, 0); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), (mvals.grout_color == FG_BG)); gtk_widget_show (toggle); g_signal_connect (toggle, "toggled", G_CALLBACK (gimp_toggle_button_update), &mvals.grout_color); gtk_widget_show (toggle_vbox); gtk_widget_show (frame); /* tiling primitive */ frame = gimp_int_radio_group_new (TRUE, _("Tiling Primitives"), G_CALLBACK (gimp_radio_button_update), &mvals.tile_type, mvals.tile_type, _("_Squares"), SQUARES, NULL, _("He_xagons"), HEXAGONS, NULL, _("Oc_tagons & squares"), OCTAGONS, NULL, NULL); gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); gtk_widget_show (frame); gtk_widget_show (vbox); /* parameter settings */ frame = gimp_frame_new (_("Settings")); gtk_box_pack_start (GTK_BOX (main_hbox), frame, TRUE, TRUE, 0); table = gtk_table_new (6, 3, FALSE); gtk_table_set_col_spacings (GTK_TABLE (table), 6); gtk_table_set_row_spacings (GTK_TABLE (table), 6); gtk_container_add (GTK_CONTAINER (frame), table); scale_data = gimp_scale_entry_new (GTK_TABLE (table), 0, 0, _("T_ile size:"), SCALE_WIDTH, 5, mvals.tile_size, 5.0, 100.0, 1.0, 10.0, 1, TRUE, 0, 0, NULL, NULL); g_signal_connect (scale_data, "value_changed", G_CALLBACK (gimp_double_adjustment_update), &mvals.tile_size); scale_data = gimp_scale_entry_new (GTK_TABLE (table), 0, 1, _("Tile _height:"), SCALE_WIDTH, 5, mvals.tile_height, 1.0, 50.0, 1.0, 10.0, 1, TRUE, 0, 0, NULL, NULL); g_signal_connect (scale_data, "value_changed", G_CALLBACK (gimp_double_adjustment_update), &mvals.tile_height); scale_data = gimp_scale_entry_new (GTK_TABLE (table), 0, 2, _("Til_e spacing:"), SCALE_WIDTH, 5, mvals.tile_spacing, 1.0, 50.0, 1.0, 10.0, 1, TRUE, 0, 0, NULL, NULL); g_signal_connect (scale_data, "value_changed", G_CALLBACK (gimp_double_adjustment_update), &mvals.tile_spacing); scale_data = gimp_scale_entry_new (GTK_TABLE (table), 0, 3, _("Tile _neatness:"), SCALE_WIDTH, 5, mvals.tile_neatness, 0.0, 1.0, 0.10, 0.1, 2, TRUE, 0, 0, NULL, NULL); g_signal_connect (scale_data, "value_changed", G_CALLBACK (gimp_double_adjustment_update), &mvals.tile_neatness); scale_data = gimp_scale_entry_new (GTK_TABLE (table), 0, 4, _("Light _direction:"), SCALE_WIDTH, 5, mvals.light_dir, 0.0, 360.0, 1.0, 15.0, 1, TRUE, 0, 0, NULL, NULL); g_signal_connect (scale_data, "value_changed", G_CALLBACK (gimp_double_adjustment_update), &mvals.light_dir); scale_data = gimp_scale_entry_new (GTK_TABLE (table), 0, 5, _("Color _variation:"), SCALE_WIDTH, 5, mvals.color_variation, 0.0, 1.0, 0.01, 0.1, 2, TRUE, 0, 0, NULL, NULL); g_signal_connect (scale_data, "value_changed", G_CALLBACK (gimp_double_adjustment_update), &mvals.color_variation); gtk_widget_show (frame); gtk_widget_show (table); gtk_widget_show (main_hbox); gtk_widget_show (dlg); run = (gimp_dialog_run (GIMP_DIALOG (dlg)) == GTK_RESPONSE_OK); gtk_widget_destroy (dlg); return run; } /* * Gradient finding machinery */ static void find_gradients (GimpDrawable *drawable, gdouble std_dev) { GimpPixelRgn src_rgn; GimpPixelRgn dest_rgn; gint bytes; gint width, height; gint i, j; guchar *gr, * dh, * dv; gint hmax, vmax; gint row, rows; gint ith_row; gint x1, y1, x2, y2; /* find the mask bounds */ gimp_drawable_mask_bounds (drawable->drawable_id, &x1, &y1, &x2, &y2); width = (x2 - x1); height = (y2 - y1); bytes = drawable->bpp; /* allocate the gradient maps */ h_grad = g_new (guchar, width * height); v_grad = g_new (guchar, width * height); m_grad = g_new (guchar, width * height); /* Calculate total number of rows to be processed */ rows = width * 2 + height * 2; ith_row = rows / 256; if (!ith_row) ith_row = 1; row = 0; /* Get the horizontal derivative */ gimp_pixel_rgn_init (&src_rgn, drawable, x1, y1, width, height, FALSE, FALSE); gimp_pixel_rgn_init (&dest_rgn, drawable, x1, y1, width, height, TRUE, TRUE); gaussian_deriv (&src_rgn, &dest_rgn, HORIZONTAL, std_dev, &row, rows, ith_row); gimp_pixel_rgn_init (&src_rgn, drawable, x1, y1, width, height, FALSE, TRUE); dest_rgn.x = dest_rgn.y = 0; dest_rgn.w = width; dest_rgn.h = height; dest_rgn.bpp = 1; dest_rgn.rowstride = width; dest_rgn.data = h_grad; find_max_gradient (&src_rgn, &dest_rgn); /* Get the vertical derivative */ gimp_pixel_rgn_init (&src_rgn, drawable, x1, y1, width, height, FALSE, FALSE); gimp_pixel_rgn_init (&dest_rgn, drawable, x1, y1, width, height, TRUE, TRUE); gaussian_deriv (&src_rgn, &dest_rgn, VERTICAL, std_dev, &row, rows, ith_row); gimp_pixel_rgn_init (&src_rgn, drawable, x1, y1, width, height, FALSE, TRUE); dest_rgn.x = dest_rgn.y = 0; dest_rgn.w = width; dest_rgn.h = height; dest_rgn.bpp = 1; dest_rgn.rowstride = width; dest_rgn.data = v_grad; find_max_gradient (&src_rgn, &dest_rgn); gimp_progress_update (1.0); /* fill in the gradient map */ gr = m_grad; dh = h_grad; dv = v_grad; for (i = 0; i < height; i++) { for (j = 0; j < width; j++, dh++, dv++, gr++) { /* Find the gradient */ if (!j || !i || (j == width - 1) || (i == height - 1)) *gr = MAG_THRESHOLD; else { hmax = *dh - 128; vmax = *dv - 128; *gr = (guchar)sqrt (SQR (hmax) + SQR (hmax)); } } } } static void find_max_gradient (GimpPixelRgn *src_rgn, GimpPixelRgn *dest_rgn) { guchar *s, *d, *s_iter, *s_end; gpointer pr; gint i, j; gint val; gint max; /* Find the maximum value amongst intensity channels */ pr = gimp_pixel_rgns_register (2, src_rgn, dest_rgn); while (pr) { s = src_rgn->data; d = dest_rgn->data; for (i = 0; i < src_rgn->h; i++) { for (j = 0; j < src_rgn->w; j++) { max = 0; #ifndef SLOW_CODE #define ABSVAL(x) ((x) >= 0 ? (x) : -(x)) for (s_iter = s, s_end = s + src_rgn->bpp; s_iter < s_end; s_iter++) { val = *s; if (ABSVAL(val) > ABSVAL(max)) max = val; } *d++ = max; #else for (b = 0; b < src_rgn->bpp; b++) { val = (gint) s[b] - 128; if (abs (val) > abs (max)) max = val; } *d++ = (max + 128); #endif s += src_rgn->bpp; } s += (src_rgn->rowstride - src_rgn->w * src_rgn->bpp); d += (dest_rgn->rowstride - dest_rgn->w); } pr = gimp_pixel_rgns_process (pr); } } /*********************************************/ /* Functions for gaussian convolutions */ /*********************************************/ static void gaussian_deriv (GimpPixelRgn *src_rgn, GimpPixelRgn *dest_rgn, gint type, gdouble std_dev, gint *prog, gint max_prog, gint ith_prog) { guchar *dest, *dp; guchar *src, *sp, *s; guchar *data; gint *buf, *b; gint chan; gint i, row, col; gint start, end; gint curve_array [9]; gint sum_array [9]; gint * curve; gint * sum; gint bytes; gint val; gint total; gint length; gint initial_p[4], initial_m[4]; gint x1, y1, x2, y2; /* get the mask bounds */ gimp_drawable_mask_bounds (src_rgn->drawable->drawable_id, &x1, &y1, &x2, &y2); bytes = src_rgn->bpp; /* allocate buffers for get/set pixel region rows/cols */ length = MAX (src_rgn->w, src_rgn->h) * bytes; data = g_new (guchar, length * 2); src = data; dest = data + length; #ifdef UNOPTIMIZED_CODE length = 3; /* static for speed */ #else /* badhack :) */ # define length 3 #endif /* initialize */ curve = curve_array + length; sum = sum_array + length; buf = g_new (gint, MAX ((x2 - x1), (y2 - y1)) * bytes); if (type == VERTICAL) { make_curve_d (curve, sum, std_dev, length); total = sum[0] * -2; } else { make_curve (curve, sum, std_dev, length); total = sum[length] + curve[length]; } for (col = x1; col < x2; col++) { gimp_pixel_rgn_get_col (src_rgn, src, col, y1, (y2 - y1)); sp = src; dp = dest; b = buf; for (chan = 0; chan < bytes; chan++) { initial_p[chan] = sp[chan]; initial_m[chan] = sp[(y2 - y1 - 1) * bytes + chan]; } for (row = y1; row < y2; row++) { start = ((row - y1) < length) ? (y1 - row) : -length; end = ((y2 - row - 1) < length) ? (y2 - row - 1) : length; for (chan = 0; chan < bytes; chan++) { s = sp + (start * bytes) + chan; val = 0; i = start; if (start != -length) val += initial_p[chan] * (sum[start] - sum[-length]); while (i <= end) { val += *s * curve[i++]; s += bytes; } if (end != length) val += initial_m[chan] * (sum[length] + curve[length] - sum[end+1]); *b++ = val / total; } sp += bytes; } b = buf; if (type == VERTICAL) for (row = y1; row < y2; row++) { for (chan = 0; chan < bytes; chan++) { b[chan] += 128; dp[chan] = CLAMP0255 (b[chan]); } b += bytes; dp += bytes; } else for (row = y1; row < y2; row++) { for (chan = 0; chan < bytes; chan++) { dp[chan] = CLAMP0255 (b[chan]); } b += bytes; dp += bytes; } gimp_pixel_rgn_set_col (dest_rgn, dest, col, y1, (y2 - y1)); if (! ((*prog)++ % ith_prog)) gimp_progress_update ((gdouble) *prog / (gdouble) max_prog); } if (type == HORIZONTAL) { make_curve_d (curve, sum, std_dev, length); total = sum[0] * -2; } else { make_curve (curve, sum, std_dev, length); total = sum[length] + curve[length]; } for (row = y1; row < y2; row++) { gimp_pixel_rgn_get_row (dest_rgn, src, x1, row, (x2 - x1)); sp = src; dp = dest; b = buf; for (chan = 0; chan < bytes; chan++) { initial_p[chan] = sp[chan]; initial_m[chan] = sp[(x2 - x1 - 1) * bytes + chan]; } for (col = x1; col < x2; col++) { start = ((col - x1) < length) ? (x1 - col) : -length; end = ((x2 - col - 1) < length) ? (x2 - col - 1) : length; for (chan = 0; chan < bytes; chan++) { s = sp + (start * bytes) + chan; val = 0; i = start; if (start != -length) val += initial_p[chan] * (sum[start] - sum[-length]); while (i <= end) { val += *s * curve[i++]; s += bytes; } if (end != length) val += initial_m[chan] * (sum[length] + curve[length] - sum[end+1]); *b++ = val / total; } sp += bytes; } b = buf; if (type == HORIZONTAL) for (col = x1; col < x2; col++) { for (chan = 0; chan < bytes; chan++) { b[chan] += 128; dp[chan] = CLAMP0255 (b[chan]); } b += bytes; dp += bytes; } else for (col = x1; col < x2; col++) { for (chan = 0; chan < bytes; chan++) { dp[chan] = CLAMP0255 (b[chan]); } b += bytes; dp += bytes; } gimp_pixel_rgn_set_row (dest_rgn, dest, x1, row, (x2 - x1)); if (! ((*prog)++ % ith_prog)) gimp_progress_update ((gdouble) *prog / (gdouble) max_prog); } g_free (buf); g_free (data); #ifndef UNOPTIMIZED_CODE /* end bad hack */ #undef length #endif } /* * The equations: g(r) = exp (- r^2 / (2 * sigma^2)) * r = sqrt (x^2 + y ^2) */ static void make_curve (gint *curve, gint *sum, gdouble sigma, gint length) { gdouble sigma2; gint i; sigma2 = sigma * sigma; curve[0] = 255; for (i = 1; i <= length; i++) { curve[i] = (gint) (exp (- (i * i) / (2 * sigma2)) * 255); curve[-i] = curve[i]; } sum[-length] = 0; for (i = -length+1; i <= length; i++) sum[i] = sum[i-1] + curve[i-1]; } /* * The equations: d_g(r) = -r * exp (- r^2 / (2 * sigma^2)) / sigma^2 * r = sqrt (x^2 + y ^2) */ static void make_curve_d (gint *curve, gint *sum, gdouble sigma, gint length) { gdouble sigma2; gint i; sigma2 = sigma * sigma; curve[0] = 0; for (i = 1; i <= length; i++) { curve[i] = (gint) ((i * exp (- (i * i) / (2 * sigma2)) / sigma2) * 255); curve[-i] = -curve[i]; } sum[-length] = 0; sum[0] = 0; for (i = 1; i <= length; i++) { sum[-length + i] = sum[-length + i - 1] + curve[-length + i - 1]; sum[i] = sum[i - 1] + curve[i - 1]; } } /*********************************************/ /* Functions for grid manipulation */ /*********************************************/ static gdouble fp_rand (gdouble val) { return g_random_double () * val; } static void grid_create_squares (gint x1, gint y1, gint x2, gint y2) { gint rows, cols; gint width, height; gint i, j; gint size = (gint) mvals.tile_size; Vertex *pt; width = x2 - x1; height = y2 - y1; rows = (height + size - 1) / size; cols = (width + size - 1) / size; grid = g_new (Vertex, (cols + 2) * (rows + 2)); grid += (cols + 2) + 1; for (i = -1; i <= rows; i++) for (j = -1; j <= cols; j++) { pt = grid + (i * (cols + 2) + j); pt->x = x1 + j * size + size/2; pt->y = y1 + i * size + size/2; } grid_rows = rows; grid_cols = cols; grid_row_pad = 1; grid_col_pad = 1; grid_multiple = 1; grid_rowstride = cols + 2; } static void grid_create_hexagons (gint x1, gint y1, gint x2, gint y2) { gint rows, cols; gint width, height; gint i, j; gdouble hex_l1, hex_l2, hex_l3; gdouble hex_width; gdouble hex_height; Vertex *pt; width = x2 - x1; height = y2 - y1; hex_l1 = mvals.tile_size / 2.0; hex_l2 = hex_l1 * 2.0 / sqrt (3.0); hex_l3 = hex_l1 / sqrt (3.0); hex_width = 6 * hex_l1 / sqrt (3.0); hex_height = mvals.tile_size; rows = ((height + hex_height - 1) / hex_height); cols = ((width + hex_width * 2 - 1) / hex_width); grid = g_new (Vertex, (cols + 2) * 4 * (rows + 2)); grid += (cols + 2) * 4 + 4; for (i = -1; i <= rows; i++) for (j = -1; j <= cols; j++) { pt = grid + (i * (cols + 2) * 4 + j * 4); pt[0].x = x1 + hex_width * j + hex_l3; pt[0].y = y1 + hex_height * i; pt[1].x = pt[0].x + hex_l2; pt[1].y = pt[0].y; pt[2].x = pt[1].x + hex_l3; pt[2].y = pt[1].y + hex_l1; pt[3].x = pt[0].x - hex_l3; pt[3].y = pt[0].y + hex_l1; } grid_rows = rows; grid_cols = cols; grid_row_pad = 1; grid_col_pad = 1; grid_multiple = 4; grid_rowstride = (cols + 2) * 4; } static void grid_create_octagons (gint x1, gint y1, gint x2, gint y2) { gint rows, cols; gint width, height; gint i, j; gdouble ts, side, leg; gdouble oct_size; Vertex *pt; width = x2 - x1; height = y2 - y1; ts = mvals.tile_size; side = ts / (sqrt (2.0) + 1.0); leg = side * sqrt (2.0) * 0.5; oct_size = ts + side; rows = ((height + oct_size - 1) / oct_size); cols = ((width + oct_size * 2 - 1) / oct_size); grid = g_new (Vertex, (cols + 2) * 8 * (rows + 2)); grid += (cols + 2) * 8 + 8; for (i = -1; i < rows + 1; i++) for (j = -1; j < cols + 1; j++) { pt = grid + (i * (cols + 2) * 8 + j * 8); pt[0].x = x1 + oct_size * j; pt[0].y = y1 + oct_size * i; pt[1].x = pt[0].x + side; pt[1].y = pt[0].y; pt[2].x = pt[0].x + leg + side; pt[2].y = pt[0].y + leg; pt[3].x = pt[2].x; pt[3].y = pt[0].y + leg + side; pt[4].x = pt[1].x; pt[4].y = pt[0].y + 2 * leg + side; pt[5].x = pt[0].x; pt[5].y = pt[4].y; pt[6].x = pt[0].x - leg; pt[6].y = pt[3].y; pt[7].x = pt[6].x; pt[7].y = pt[2].y; } grid_rows = rows; grid_cols = cols; grid_row_pad = 1; grid_col_pad = 1; grid_multiple = 8; grid_rowstride = (cols + 2) * 8; } static void grid_localize (gint x1, gint y1, gint x2, gint y2) { gint width, height; gint i, j; gint k, l; gint x3, y3, x4, y4; gint size; gint max_x, max_y; gint max; guchar * data; gdouble rand_localize; Vertex * pt; width = x2 - x1; height = y2 - y1; size = (gint) mvals.tile_size; rand_localize = size * (1.0 - mvals.tile_neatness); for (i = -grid_row_pad; i < grid_rows + grid_row_pad; i++) for (j = -grid_col_pad * grid_multiple; j < (grid_cols + grid_col_pad) * grid_multiple; j++) { pt = grid + (i * grid_rowstride + j); max_x = pt->x + (gint) (fp_rand (rand_localize) - rand_localize/2.0); max_y = pt->y + (gint) (fp_rand (rand_localize) - rand_localize/2.0); x3 = pt->x - (gint) (rand_localize / 2.0); y3 = pt->y - (gint) (rand_localize / 2.0); x4 = x3 + (gint) rand_localize; y4 = y3 + (gint) rand_localize; if (x3 < x1) x3 = x1; else if (x3 >= x2) x3 = (x2 - 1); if (y3 < y1) y3 = y1; else if (y3 >= y2) y3 = (y2 - 1); if (x4 >= x2) x4 = (x2 - 1); else if (x4 < x1) x4 = x1; if (y4 >= y2) y4 = (y2 - 1); else if (y4 < y1) y4 = y1; max = *(m_grad + (y3 - y1) * width + (x3 - x1)); data = m_grad + width * (y3 - y1); for (k = y3; k <= y4; k++) { for (l = x3; l <= x4; l++) { if (data[l] > max) { max_y = k; max_x = l; max = data[(l - x1)]; } } data += width; } pt->x = max_x; pt->y = max_y; } } static void grid_render (GimpDrawable *drawable) { GimpPixelRgn src_rgn; gint i, j, k; gint x1, y1, x2, y2; guchar *dest, *d; guchar col[4]; gint bytes; gint size, frac_size; gint count; gint index; gint vary; Polygon poly; gpointer pr; gimp_drawable_mask_bounds (drawable->drawable_id, &x1, &y1, &x2, &y2); bytes = drawable->bpp; /* Fill the image with the background color */ gimp_pixel_rgn_init (&src_rgn, drawable, x1, y1, (x2 - x1), (y2 - y1), TRUE, TRUE); for (pr = gimp_pixel_rgns_register (1, &src_rgn); pr != NULL; pr = gimp_pixel_rgns_process (pr)) { size = src_rgn.w * src_rgn.h; dest = src_rgn.data; for (i = 0; i < src_rgn.h ; i++) { d = dest; for (j = 0; j < src_rgn.w ; j++) { for (k = 0; k < bytes; k++) d[k] = back[k]; d += bytes; } dest += src_rgn.rowstride; } } size = (grid_rows + grid_row_pad) * (grid_cols + grid_col_pad); frac_size = (gint) (size * mvals.color_variation); count = 0; for (i = -grid_row_pad; i < grid_rows; i++) for (j = -grid_col_pad; j < grid_cols; j++) { vary = ((g_random_int_range (0, size)) < frac_size) ? 1 : 0; index = i * grid_rowstride + j * grid_multiple; switch (mvals.tile_type) { case SQUARES: polygon_reset (&poly); polygon_add_point (&poly, grid[index].x, grid[index].y); polygon_add_point (&poly, grid[index + 1].x, grid[index + 1].y); polygon_add_point (&poly, grid[index + grid_rowstride + 1].x, grid[index + grid_rowstride + 1].y); polygon_add_point (&poly, grid[index + grid_rowstride].x, grid[index + grid_rowstride].y); process_poly (&poly, mvals.tile_allow_split, drawable, col, vary); break; case HEXAGONS: /* The main hexagon */ polygon_reset (&poly); polygon_add_point (&poly, grid[index].x, grid[index].y); polygon_add_point (&poly, grid[index + 1].x, grid[index + 1].y); polygon_add_point (&poly, grid[index + 2].x, grid[index + 2].y); polygon_add_point (&poly, grid[index + grid_rowstride + 1].x, grid[index + grid_rowstride + 1].y); polygon_add_point (&poly, grid[index + grid_rowstride].x, grid[index + grid_rowstride].y); polygon_add_point (&poly, grid[index + 3].x, grid[index + 3].y); process_poly (&poly, mvals.tile_allow_split, drawable, col, vary); /* The auxillary hexagon */ polygon_reset (&poly); polygon_add_point (&poly, grid[index + 2].x, grid[index + 2].y); polygon_add_point (&poly, grid[index + grid_multiple * 2 - 1].x, grid[index + grid_multiple * 2 - 1].y); polygon_add_point (&poly, grid[index + grid_rowstride + grid_multiple].x, grid[index + grid_rowstride + grid_multiple].y); polygon_add_point (&poly, grid[index + grid_rowstride + grid_multiple + 3].x, grid[index + grid_rowstride + grid_multiple + 3].y); polygon_add_point (&poly, grid[index + grid_rowstride + 2].x, grid[index + grid_rowstride + 2].y); polygon_add_point (&poly, grid[index + grid_rowstride + 1].x, grid[index + grid_rowstride + 1].y); process_poly (&poly, mvals.tile_allow_split, drawable, col, vary); break; case OCTAGONS: /* The main octagon */ polygon_reset (&poly); for (k = 0; k < 8; k++) polygon_add_point (&poly, grid[index + k].x, grid[index + k].y); process_poly (&poly, mvals.tile_allow_split, drawable, col, vary); /* The auxillary octagon */ polygon_reset (&poly); polygon_add_point (&poly, grid[index + 3].x, grid[index + 3].y); polygon_add_point (&poly, grid[index + grid_multiple * 2 - 2].x, grid[index + grid_multiple * 2 - 2].y); polygon_add_point (&poly, grid[index + grid_multiple * 2 - 3].x, grid[index + grid_multiple * 2 - 3].y); polygon_add_point (&poly, grid[index + grid_rowstride + grid_multiple].x, grid[index + grid_rowstride + grid_multiple].y); polygon_add_point (&poly, grid[index + grid_rowstride + grid_multiple * 2 - 1].x, grid[index + grid_rowstride + grid_multiple * 2 - 1].y); polygon_add_point (&poly, grid[index + grid_rowstride + 2].x, grid[index + grid_rowstride + 2].y); polygon_add_point (&poly, grid[index + grid_rowstride + 1].x, grid[index + grid_rowstride + 1].y); polygon_add_point (&poly, grid[index + 4].x, grid[index + 4].y); process_poly (&poly, mvals.tile_allow_split, drawable, col, vary); /* The main square */ polygon_reset (&poly); polygon_add_point (&poly, grid[index + 2].x, grid[index + 2].y); polygon_add_point (&poly, grid[index + grid_multiple * 2 - 1].x, grid[index + grid_multiple * 2 - 1].y); polygon_add_point (&poly, grid[index + grid_multiple * 2 - 2].x, grid[index + grid_multiple * 2 - 2].y); polygon_add_point (&poly, grid[index + 3].x, grid[index + 3].y); process_poly (&poly, FALSE, drawable, col, vary); /* The auxillary square */ polygon_reset (&poly); polygon_add_point (&poly, grid[index + 5].x, grid[index + 5].y); polygon_add_point (&poly, grid[index + 4].x, grid[index + 4].y); polygon_add_point (&poly, grid[index + grid_rowstride + 1].x, grid[index + grid_rowstride + 1].y); polygon_add_point (&poly, grid[index + grid_rowstride].x, grid[index + grid_rowstride].y); process_poly (&poly, FALSE, drawable, col, vary); break; } gimp_progress_update ((double) count++ / (double) size); } gimp_progress_update (1.0); } static void process_poly (Polygon *poly, gint allow_split, GimpDrawable *drawable, guchar *col, gint vary) { gdouble dir[2]; gdouble loc[2]; gdouble cx, cy; gdouble magnitude; gdouble distance; gdouble color_vary; gint x1, y1, x2, y2; /* find mask bounds */ gimp_drawable_mask_bounds (drawable->drawable_id, &x1, &y1, &x2, &y2); /* determine the variation of tile color based on tile number */ color_vary = (vary) ? fp_rand (mvals.color_variation) : 0; color_vary = (g_random_int_range (0, 2)) ? color_vary * 127 : -color_vary * 127; /* Determine direction of edges inside polygon, if any */ find_poly_dir (poly, m_grad, h_grad, v_grad, dir, loc, x1, y1, x2, y2); magnitude = sqrt (SQR (dir[0] - 128) + SQR (dir[1] - 128)); /* Find the center of the polygon */ polygon_find_center (poly, &cx, &cy); distance = sqrt (SQR (loc[0] - cx) + SQR (loc[1] - cy)); /* If the magnitude of direction inside the polygon is greater than * THRESHOLD, split the polygon into two new polygons */ if (magnitude > MAG_THRESHOLD && (2 * distance / mvals.tile_size) < 0.5 && allow_split) split_poly (poly, drawable, col, dir, color_vary); /* Otherwise, render the original polygon */ else render_poly (poly, drawable, col, color_vary); } static void render_poly (Polygon *poly, GimpDrawable *drawable, guchar *col, gdouble vary) { gdouble cx, cy; polygon_find_center (poly, &cx, &cy); if (mvals.color_averaging) find_poly_color (poly, drawable, col, vary); scale_poly (poly, cx, cy, scale); if (mvals.color_averaging) fill_poly_color (poly, drawable, col); else fill_poly_image (poly, drawable, vary); } static void split_poly (Polygon *poly, GimpDrawable *drawable, guchar *col, gdouble *dir, gdouble vary) { Polygon new_poly; gdouble spacing; gdouble cx, cy; gdouble magnitude; gdouble vec[2]; gdouble pt[2]; spacing = mvals.tile_spacing / (2.0 * scale); polygon_find_center (poly, &cx, &cy); polygon_translate (poly, -cx, -cy); magnitude = sqrt (SQR (dir[0] - 128) + SQR (dir[1] - 128)); vec[0] = -(dir[1] - 128) / magnitude; vec[1] = (dir[0] - 128) / magnitude; pt[0] = -vec[1] * spacing; pt[1] = vec[0] * spacing; polygon_reset (&new_poly); clip_poly (vec, pt, poly, &new_poly); polygon_translate (&new_poly, cx, cy); if (new_poly.npts) { if (mvals.color_averaging) find_poly_color (&new_poly, drawable, col, vary); scale_poly (&new_poly, cx, cy, scale); if (mvals.color_averaging) fill_poly_color (&new_poly, drawable, col); else fill_poly_image (&new_poly, drawable, vary); } vec[0] = -vec[0]; vec[1] = -vec[1]; pt[0] = -pt[0]; pt[1] = -pt[1]; polygon_reset (&new_poly); clip_poly (vec, pt, poly, &new_poly); polygon_translate (&new_poly, cx, cy); if (new_poly.npts) { if (mvals.color_averaging) find_poly_color (&new_poly, drawable, col, vary); scale_poly (&new_poly, cx, cy, scale); if (mvals.color_averaging) fill_poly_color (&new_poly, drawable, col); else fill_poly_image (&new_poly, drawable, vary); } } static void clip_poly (gdouble *dir, gdouble *pt, Polygon *poly, Polygon *poly_new) { gint i; gdouble x1, y1, x2, y2; for (i = 0; i < poly->npts; i++) { x1 = (i) ? poly->pts[i-1].x : poly->pts[poly->npts-1].x; y1 = (i) ? poly->pts[i-1].y : poly->pts[poly->npts-1].y; x2 = poly->pts[i].x; y2 = poly->pts[i].y; clip_point (dir, pt, x1, y1, x2, y2, poly_new); } } static void clip_point (gdouble *dir, gdouble *pt, gdouble x1, gdouble y1, gdouble x2, gdouble y2, Polygon *poly_new) { gdouble det, m11, m12, m21, m22; gdouble side1, side2; gdouble t; gdouble vec[2]; x1 -= pt[0]; x2 -= pt[0]; y1 -= pt[1]; y2 -= pt[1]; side1 = x1 * -dir[1] + y1 * dir[0]; side2 = x2 * -dir[1] + y2 * dir[0]; /* If both points are to be clipped, ignore */ if (side1 < 0.0 && side2 < 0.0) return; /* If both points are non-clipped, set point */ else if (side1 >= 0.0 && side2 >= 0.0) { polygon_add_point (poly_new, x2 + pt[0], y2 + pt[1]); return; } /* Otherwise, there is an intersection... */ else { vec[0] = x1 - x2; vec[1] = y1 - y2; det = dir[0] * vec[1] - dir[1] * vec[0]; if (det == 0.0) { polygon_add_point (poly_new, x2 + pt[0], y2 + pt[1]); return; } m11 = vec[1] / det; m12 = -vec[0] / det; m21 = -dir[1] / det; m22 = dir[0] / det; t = m11 * x1 + m12 * y1; /* If the first point is clipped, set intersection and point */ if (side1 < 0.0 && side2 > 0.0) { polygon_add_point (poly_new, dir[0] * t + pt[0], dir[1] * t + pt[1]); polygon_add_point (poly_new, x2 + pt[0], y2 + pt[1]); } else polygon_add_point (poly_new, dir[0] * t + pt[0], dir[1] * t + pt[1]); } } static void find_poly_dir (Polygon *poly, guchar *m_gr, guchar *h_gr, guchar *v_gr, gdouble *dir, gdouble *loc, gint x1, gint y1, gint x2, gint y2) { gdouble dmin_x, dmin_y; gdouble dmax_x, dmax_y; gint xs, ys; gint xe, ye; gint min_x, min_y; gint max_x, max_y; gint size_x, size_y; gint *max_scanlines; gint *min_scanlines; guchar *dm, *dv, *dh; gint count, total; gint rowstride; gint i, j; rowstride = (x2 - x1); count = 0; total = 0; dir[0] = 0.0; dir[1] = 0.0; loc[0] = 0.0; loc[1] = 0.0; polygon_extents (poly, &dmin_x, &dmin_y, &dmax_x, &dmax_y); min_x = (gint) dmin_x; min_y = (gint) dmin_y; max_x = (gint) dmax_x; max_y = (gint) dmax_y; size_y = max_y - min_y; size_x = max_x - min_x; min_scanlines = g_new (gint, size_y); max_scanlines = g_new (gint, size_y); for (i = 0; i < size_y; i++) { min_scanlines[i] = max_x; max_scanlines[i] = min_x; } for (i = 0; i < poly->npts; i++) { xs = (gint) ((i) ? poly->pts[i-1].x : poly->pts[poly->npts-1].x); ys = (gint) ((i) ? poly->pts[i-1].y : poly->pts[poly->npts-1].y); xe = (gint) poly->pts[i].x; ye = (gint) poly->pts[i].y; convert_segment (xs, ys, xe, ye, min_y, min_scanlines, max_scanlines); } for (i = 0; i < size_y; i++) { if ((i + min_y) >= y1 && (i + min_y) < y2) { dm = m_gr + (i + min_y - y1) * rowstride - x1; dh = h_gr + (i + min_y - y1) * rowstride - x1; dv = v_gr + (i + min_y - y1) * rowstride - x1; for (j = min_scanlines[i]; j < max_scanlines[i]; j++) { if (j >= x1 && j < x2) { if (dm[j] > MAG_THRESHOLD) { dir[0] += dh[j]; dir[1] += dv[j]; loc[0] += j; loc[1] += i + min_y; count++; } total++; } } } } if (!total) return; if ((gdouble) count / (gdouble) total > COUNT_THRESHOLD) { dir[0] /= count; dir[1] /= count; loc[0] /= count; loc[1] /= count; } else { dir[0] = 128.0; dir[1] = 128.0; loc[0] = 0.0; loc[1] = 0.0; } g_free (min_scanlines); g_free (max_scanlines); } static void find_poly_color (Polygon *poly, GimpDrawable *drawable, guchar *col, gdouble color_var) { GimpPixelRgn src_rgn; gdouble dmin_x, dmin_y; gdouble dmax_x, dmax_y; gint xs, ys; gint xe, ye; gint min_x, min_y; gint max_x, max_y; gint size_x, size_y; gint * max_scanlines; gint * min_scanlines; gint col_sum[4] = {0, 0, 0, 0}; gint bytes; gint b, count; gint i, j, y; gint x1, y1, x2, y2; count = 0; gimp_drawable_mask_bounds (drawable->drawable_id, &x1, &y1, &x2, &y2); bytes = drawable->bpp; polygon_extents (poly, &dmin_x, &dmin_y, &dmax_x, &dmax_y); min_x = (gint) dmin_x; min_y = (gint) dmin_y; max_x = (gint) dmax_x; max_y = (gint) dmax_y; size_y = max_y - min_y; size_x = max_x - min_x; min_scanlines = g_new (int, size_y); max_scanlines = g_new (int, size_y); for (i = 0; i < size_y; i++) { min_scanlines[i] = max_x; max_scanlines[i] = min_x; } for (i = 0; i < poly->npts; i++) { xs = (gint) ((i) ? poly->pts[i-1].x : poly->pts[poly->npts-1].x); ys = (gint) ((i) ? poly->pts[i-1].y : poly->pts[poly->npts-1].y); xe = (gint) poly->pts[i].x; ye = (gint) poly->pts[i].y; convert_segment (xs, ys, xe, ye, min_y, min_scanlines, max_scanlines); } gimp_pixel_rgn_init (&src_rgn, drawable, 0, 0, drawable->width, drawable->height, FALSE, FALSE); for (i = 0; i < size_y; i++) { y = i + min_y; if (y >= y1 && y < y2) { for (j = min_scanlines[i]; j < max_scanlines[i]; j++) { if (j >= x1 && j < x2) { gimp_pixel_rgn_get_pixel (&src_rgn, col, j, y); for (b = 0; b < bytes; b++) col_sum[b] += col[b]; count++; } } } } if (count) for (b = 0; b < bytes; b++) { col_sum[b] = (gint) (col_sum[b] / count + color_var); col[b] = CLAMP0255 (col_sum[b]); } g_free (min_scanlines); g_free (max_scanlines); } static void scale_poly (Polygon *poly, gdouble tx, gdouble ty, gdouble poly_scale) { polygon_translate (poly, -tx, -ty); polygon_scale (poly, poly_scale); polygon_translate (poly, tx, ty); } static void fill_poly_color (Polygon *poly, GimpDrawable *drawable, guchar *col) { GimpPixelRgn src_rgn; gdouble dmin_x, dmin_y; gdouble dmax_x, dmax_y; gint xs, ys; gint xe, ye; gint min_x, min_y; gint max_x, max_y; gint size_x, size_y; gint * max_scanlines, *max_scanlines_iter; gint * min_scanlines, *min_scanlines_iter; gint * vals; gint val; gint pixel; gint bytes; guchar buf[4]; gint b, i, j, k, x, y; gdouble contrib; gdouble xx, yy; gint supersample; gint supersample2; gint x1, y1, x2, y2; Vertex *pts_tmp; const gint poly_npts = poly->npts; /* Determine antialiasing */ if (mvals.antialiasing) { supersample = SUPERSAMPLE; supersample2 = SQR (supersample); } else { supersample = supersample2 = 1; } gimp_drawable_mask_bounds (drawable->drawable_id, &x1, &y1, &x2, &y2); bytes = drawable->bpp; /* begin loop */ if(poly_npts) { pts_tmp = poly->pts; xs = (gint) pts_tmp[poly_npts-1].x; ys = (gint) pts_tmp[poly_npts-1].y; xe = (gint) pts_tmp->x; ye = (gint) pts_tmp->y; calc_spec_vec (vecs, xs, ys, xe, ye); for (i = 1; i < poly_npts; i++) { xs = (gint) (pts_tmp->x); ys = (gint) (pts_tmp->y); pts_tmp++; xe = (gint) pts_tmp->x; ye = (gint) pts_tmp->y; calc_spec_vec (vecs+i, xs, ys, xe, ye); } } /* end loop */ polygon_extents (poly, &dmin_x, &dmin_y, &dmax_x, &dmax_y); min_x = (gint) dmin_x; min_y = (gint) dmin_y; max_x = (gint) dmax_x; max_y = (gint) dmax_y; size_y = (max_y - min_y) * supersample; size_x = (max_x - min_x) * supersample; min_scanlines = min_scanlines_iter = g_new (gint, size_y); max_scanlines = max_scanlines_iter = g_new (gint, size_y); for (i = 0; i < size_y; i++) { min_scanlines[i] = max_x * supersample; max_scanlines[i] = min_x * supersample; } /* begin loop */ if(poly_npts) { pts_tmp = poly->pts; xs = (gint) pts_tmp[poly_npts-1].x; ys = (gint) pts_tmp[poly_npts-1].y; xe = (gint) pts_tmp->x; ye = (gint) pts_tmp->y; xs *= supersample; ys *= supersample; xe *= supersample; ye *= supersample; convert_segment (xs, ys, xe, ye, min_y * supersample, min_scanlines, max_scanlines); for (i = 1; i < poly_npts; i++) { xs = (gint) pts_tmp->x; ys = (gint) pts_tmp->y; pts_tmp++; xe = (gint) pts_tmp->x; ye = (gint) pts_tmp->y; xs *= supersample; ys *= supersample; xe *= supersample; ye *= supersample; convert_segment (xs, ys, xe, ye, min_y * supersample, min_scanlines, max_scanlines); } } /* end loop */ gimp_pixel_rgn_init (&src_rgn, drawable, 0, 0, drawable->width, drawable->height, TRUE, TRUE); vals = g_new (gint, size_x); for (i = 0; i < size_y; i++, min_scanlines_iter++, max_scanlines_iter++) { if (! (i % supersample)) memset (vals, 0, sizeof (gint) * size_x); yy = (gdouble) i / (gdouble) supersample + min_y; for (j = *min_scanlines_iter; j < *max_scanlines_iter; j++) { x = j - min_x * supersample; vals[x] += 255; } if (! ((i + 1) % supersample)) { y = (i / supersample) + min_y; if (y >= y1 && y < y2) { for (j = 0; j < size_x; j += supersample) { x = (j / supersample) + min_x; if (x >= x1 && x < x2) { val = 0; for (k = 0; k < supersample; k++) val += vals[j + k]; val /= supersample2; if (val > 0) { xx = (gdouble) j / (gdouble) supersample + min_x; contrib = calc_spec_contrib (vecs, poly_npts, xx, yy); for (b = 0; b < bytes; b++) { pixel = col[b] + (gint) (((contrib < 0.0)?(col[b] - back[b]):(fore[b] - col[b])) * contrib); buf[b] = ((pixel * val) + (back[b] * (255 - val))) / 255; } gimp_pixel_rgn_set_pixel (&src_rgn, buf, x, y); } } } } } } g_free (vals); g_free (min_scanlines); g_free (max_scanlines); } static void fill_poly_image (Polygon *poly, GimpDrawable *drawable, gdouble vary) { GimpPixelRgn src_rgn, dest_rgn; gdouble dmin_x, dmin_y; gdouble dmax_x, dmax_y; gint xs, ys; gint xe, ye; gint min_x, min_y; gint max_x, max_y; gint size_x, size_y; gint * max_scanlines; gint * min_scanlines; gint * vals; gint val; gint pixel; gint bytes; guchar buf[4]; gint b, i, j, k, x, y; gdouble contrib; gdouble xx, yy; gint supersample; gint supersample2; gint x1, y1, x2, y2; /* Determine antialiasing */ if (mvals.antialiasing) { supersample = SUPERSAMPLE; supersample2 = SQR (supersample); } else { supersample = supersample2 = 1; } gimp_drawable_mask_bounds (drawable->drawable_id, &x1, &y1, &x2, &y2); bytes = drawable->bpp; for (i = 0; i < poly->npts; i++) { xs = (gint) ((i) ? poly->pts[i-1].x : poly->pts[poly->npts-1].x); ys = (gint) ((i) ? poly->pts[i-1].y : poly->pts[poly->npts-1].y); xe = (gint) poly->pts[i].x; ye = (gint) poly->pts[i].y; calc_spec_vec (vecs+i, xs, ys, xe, ye); } polygon_extents (poly, &dmin_x, &dmin_y, &dmax_x, &dmax_y); min_x = (gint) dmin_x; min_y = (gint) dmin_y; max_x = (gint) dmax_x; max_y = (gint) dmax_y; size_y = (max_y - min_y) * supersample; size_x = (max_x - min_x) * supersample; min_scanlines = g_new (gint, size_y); max_scanlines = g_new (gint, size_y); for (i = 0; i < size_y; i++) { min_scanlines[i] = max_x * supersample; max_scanlines[i] = min_x * supersample; } for (i = 0; i < poly->npts; i++) { xs = (gint) ((i) ? poly->pts[i-1].x : poly->pts[poly->npts-1].x); ys = (gint) ((i) ? poly->pts[i-1].y : poly->pts[poly->npts-1].y); xe = (gint) poly->pts[i].x; ye = (gint) poly->pts[i].y; xs *= supersample; ys *= supersample; xe *= supersample; ye *= supersample; convert_segment (xs, ys, xe, ye, min_y * supersample, min_scanlines, max_scanlines); } gimp_pixel_rgn_init (&src_rgn, drawable, 0, 0, drawable->width, drawable->height, FALSE, FALSE); gimp_pixel_rgn_init (&dest_rgn, drawable, 0, 0, drawable->width, drawable->height, TRUE, TRUE); vals = g_new (gint, size_x); for (i = 0; i < size_y; i++) { if (! (i % supersample)) memset (vals, 0, sizeof (gint) * size_x); yy = (gdouble) i / (gdouble) supersample + min_y; for (j = min_scanlines[i]; j < max_scanlines[i]; j++) { x = j - min_x * supersample; vals[x] += 255; } if (! ((i + 1) % supersample)) { y = (i / supersample) + min_y; if (y >= y1 && y < y2) { for (j = 0; j < size_x; j += supersample) { x = (j / supersample) + min_x; if (x >= x1 && x < x2) { val = 0; for (k = 0; k < supersample; k++) val += vals[j + k]; val /= supersample2; if (val > 0) { xx = (double) j / (double) supersample + min_x; contrib = calc_spec_contrib (vecs, poly->npts, xx, yy); gimp_pixel_rgn_get_pixel (&src_rgn, buf, x, y); for (b = 0; b < bytes; b++) { if (contrib < 0.0) pixel = buf[b] + (int) ((buf[b] - back[b]) * contrib); else pixel = buf[b] + (int) ((fore[b] - buf[b]) * contrib); /* factor in per-tile intensity variation */ pixel += vary; if (pixel > 255) pixel = 255; if (pixel < 0) pixel = 0; buf[b] = ((pixel * val) + (back[b] * (255 - val))) / 255; } gimp_pixel_rgn_set_pixel (&dest_rgn, buf, x, y); } } } } } } g_free (vals); g_free (min_scanlines); g_free (max_scanlines); } static void calc_spec_vec (SpecVec *vec, gint x1, gint y1, gint x2, gint y2) { gdouble r; vec->base_x = x1; vec->base_y = y1; r = sqrt (SQR (x2 - x1) + SQR (y2 - y1)); if (r > 0.0) { vec->norm_x = -(y2 - y1) / r; vec->norm_y = (x2 - x1) / r; } else { vec->norm_x = 0; vec->norm_y = 0; } vec->light = vec->norm_x * light_x + vec->norm_y * light_y; } static double calc_spec_contrib (SpecVec *vecs, gint n, gdouble x, gdouble y) { gint i; gdouble contrib = 0; for (i = 0; i < n; i++) { gdouble x_p, y_p; gdouble dist; x_p = x - vecs[i].base_x; y_p = y - vecs[i].base_y; dist = fabs (x_p * vecs[i].norm_x + y_p * vecs[i].norm_y); if (mvals.tile_surface == ROUGH) { /* If the surface is rough, randomly perturb the distance */ dist -= dist * g_random_double (); } /* If the distance to an edge is less than the tile_spacing, there * will be no highlight as the tile blends to background here */ if (dist < 1.0) contrib += vecs[i].light; else if (dist <= mvals.tile_height) contrib += vecs[i].light * (1.0 - (dist / mvals.tile_height)); } return contrib / 4.0; } static void convert_segment (gint x1, gint y1, gint x2, gint y2, gint offset, gint *min, gint *max) { gint ydiff, y, tmp; gdouble xinc, xstart; if (y1 > y2) { tmp = y2; y2 = y1; y1 = tmp; tmp = x2; x2 = x1; x1 = tmp; } ydiff = y2 - y1; if (ydiff) { xinc = (gdouble) (x2 - x1) / (gdouble) ydiff; xstart = x1 + 0.5 * xinc; for (y = y1 ; y < y2; y++) { if (xstart < min[y - offset]) min[y - offset] = xstart; if (xstart > max[y - offset]) max[y - offset] = xstart; xstart += xinc; } } } static void polygon_add_point (Polygon *poly, gdouble x, gdouble y) { if (poly->npts < 12) { poly->pts[poly->npts].x = x; poly->pts[poly->npts].y = y; poly->npts++; } else g_print ( _("Unable to add additional point.\n")); } static int polygon_find_center (Polygon *poly, gdouble *cx, gdouble *cy) { gint i; if (!poly->npts) return 0; *cx = 0.0; *cy = 0.0; for (i = 0; i < poly->npts; i++) { *cx += poly->pts[i].x; *cy += poly->pts[i].y; } *cx /= poly->npts; *cy /= poly->npts; return 1; } static void polygon_translate (Polygon *poly, gdouble tx, gdouble ty) { gint i; for (i = 0; i < poly->npts; i++) { poly->pts[i].x += tx; poly->pts[i].y += ty; } } static void polygon_scale (Polygon *poly, gdouble poly_scale) { gint i; for (i = 0; i < poly->npts; i++) { poly->pts[i].x *= poly_scale; poly->pts[i].y *= poly_scale; } } static gint polygon_extents (Polygon *poly, gdouble *x1, gdouble *y1, gdouble *x2, gdouble *y2) { gint i; if (!poly->npts) return 0; *x1 = *x2 = poly->pts[0].x; *y1 = *y2 = poly->pts[0].y; for (i = 1; i < poly->npts; i++) { if (poly->pts[i].x < *x1) *x1 = poly->pts[i].x; if (poly->pts[i].x > *x2) *x2 = poly->pts[i].x; if (poly->pts[i].y < *y1) *y1 = poly->pts[i].y; if (poly->pts[i].y > *y2) *y2 = poly->pts[i].y; } return 1; } static void polygon_reset (Polygon *poly) { poly->npts = 0; }