From a7b206b5c327cf9c627ae474f5eccf6397994d0d Mon Sep 17 00:00:00 2001 From: Manish Singh Date: Mon, 18 May 1998 00:33:13 +0000 Subject: [PATCH] updated maze plugin * plug-ins/maze: updated maze plugin * plug-ins/script-fu/scripts/circuit.scm: reflected maze pdb interface change -Yosh --- ChangeLog | 7 + plug-ins/maze/Makefile.am | 2 +- plug-ins/maze/algorithms.c | 586 ++++++++++++++++++ plug-ins/maze/handy.c | 228 +++++++ plug-ins/maze/maze.c | 804 ++++++++++++------------- plug-ins/maze/maze.h | 29 +- plug-ins/maze/maze_face.c | 84 ++- plug-ins/script-fu/scripts/circuit.scm | 2 +- 8 files changed, 1289 insertions(+), 453 deletions(-) create mode 100644 plug-ins/maze/algorithms.c create mode 100644 plug-ins/maze/handy.c diff --git a/ChangeLog b/ChangeLog index d1532cee1e..b7cf96908e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +Sun May 17 17:31:05 PDT 1998 Manish Singh + + * plug-ins/maze: updated maze plugin + + * plug-ins/script-fu/scripts/circuit.scm: reflected maze pdb + interface change + Sun May 17 14:44:32 PDT 1998 Manish Singh * app/gimage.c: fix for merging indexed and indexed-alpha diff --git a/plug-ins/maze/Makefile.am b/plug-ins/maze/Makefile.am index dd0c547672..3febfed557 100644 --- a/plug-ins/maze/Makefile.am +++ b/plug-ins/maze/Makefile.am @@ -5,7 +5,7 @@ pluginlibdir = $(gimpplugindir)/plug-ins pluginlib_PROGRAMS = maze maze_SOURCES = \ - maze.c maze.h maze_face.c + maze.c maze.h maze_face.c algorithms.c handy.c INCLUDES = \ $(X_CFLAGS) \ diff --git a/plug-ins/maze/algorithms.c b/plug-ins/maze/algorithms.c new file mode 100644 index 0000000000..1f7b49d4de --- /dev/null +++ b/plug-ins/maze/algorithms.c @@ -0,0 +1,586 @@ +/* algorithms.c + * Contains routines for generating mazes, somewhat intertwined with + * Gimp plug-in-maze specific stuff. + * + * Kevin Turner + * http://www.poboxes.com/kevint/gimp/maze.html + */ + +/* mazegen code from rec.games.programmer's maze-faq: + * * maz.c - generate a maze + * * + * * algorithm posted to rec.games.programmer by jallen@ic.sunysb.edu + * * program cleaned and reorganized by mzraly@ldbvax.dnet.lotus.com + * * + * * don't make people pay for this, or I'll jump up and down and + * * yell and scream and embarass you in front of your friends... + */ + +/* I've put a HTMLized version of the FAQ up at + * http://www.poboxes.com/kevint/gimp/maze-faq/maze-faq.html + */ + +/* + * 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. + * + */ + +#include "maze.h" +#include "libgimp/gimp.h" +#include "libgimp/gimpui.h" + +extern MazeValues mvals; + +void mazegen(gint pos, + gchar *maz, + gint x, + gint y, + gint rnd); +void mazegen_tileable(gint pos, + gchar *maz, + gint x, + gint y, + gint rnd); +void prim(guint pos, + gchar *maz, + guint x, + guint y, + gint rnd); +void prim_tileable(gchar *maz, + guint x, + guint y, + gint rnd); + +#define ABSMOD(A,B) ( ((A) < 0) ? (((B) + (A)) % (B)) : ((A) % (B)) ) + +/* Since we are using a 1D array on 2D space, we need to do our own + calculations. (Ok, so there are ways of doing dynamically allocated + 2D arrays, but we started this way, so let's stick with it. */ + +/* The difference between CELL_* and WALL_* is that cell moves two spaces, + while wall moves one. */ + +/* Macros assume that x and y will be defined where they are used. */ +/* A return of -1 means "no such place, don't go there". */ +#define CELL_UP(POS) ((POS) < (x*2) ? -1 : (POS) - x - x) +#define CELL_DOWN(POS) ((POS) >= x*(y-2) ? -1 : (POS) + x + x) +#define CELL_LEFT(POS) (((POS) % x) <= 1 ? -1 : (POS) - 2) +#define CELL_RIGHT(POS) (((POS) % x) >= (x - 2) ? -1 : (POS) + 2) + +/* With walls, we don't need to check for boundaries, since we are + assured that there *is* a valid cell on the other side of the + wall. */ +#define WALL_UP(POS) ((POS) - x) +#define WALL_DOWN(POS) ((POS) + x) +#define WALL_LEFT(POS) ((POS) - 1) +#define WALL_RIGHT(POS) ((POS) + 1) + +/***** For tileable mazes *****/ + +#define CELL_UP_TILEABLE(POS) ((POS) < (x*2) ? x*(y-2)+(POS) : (POS) - x - x) +#define CELL_DOWN_TILEABLE(POS) ((POS) >= x*(y-2) ? (POS) - x*(y-2) : (POS) + x + x) +#define CELL_LEFT_TILEABLE(POS) (((POS) % x) <= 1 ? (POS) + x - 2 : (POS) - 2) +#define CELL_RIGHT_TILEABLE(POS) (((POS) % x) >= (x - 2) ? (POS) + 2 - x : (POS) + 2) +/* Up and left need checks, but down and right should never have to + wrap on an even sized maze. */ +#define WALL_UP_TILEABLE(POS) ((POS) < x ? x*(y-1)+(POS) : (POS) - x) +#define WALL_DOWN_TILEABLE(POS) ((POS) + x) +#define WALL_LEFT_TILEABLE(POS) (((POS) % x) == 0 ? (POS) + x - 1 : (POS) - 1) +#define WALL_RIGHT_TILEABLE(POS) ((POS) + 1) + +/* Down and right with checks. +#define WALL_DOWN_TILEABLE(POS) ((POS) >= x*(y-1) ? (POS) - x * (y-1) : (POS) + x) +#define WALL_RIGHT_TILEABLE(POS) (((POS) % x) == (x - 1) ? (POS) + 1 - x : (POS) + 1) +*/ + +/* The Incredible Recursive Maze Generation Routine */ +/* Ripped from rec.programmers.games maze-faq */ +/* Modified and commented by me, Kevin Turner. */ +void +mazegen(gint pos, gchar *maz, gint x, gint y, gint rnd) +{ + gchar d, i; + gint c=0, j=1; + + /* Punch a hole here... */ + maz[pos] = IN; + + /* If there is a wall two rows above us, bit 1 is 1. */ + while((d= (pos <= (x * 2) ? 0 : (maz[pos - x - x ] ? 0 : 1)) + /* If there is a wall two rows below us, bit 2 is 1. */ + | (pos >= x * (y - 2) ? 0 : (maz[pos + x + x] ? 0 : 2)) + /* If there is a wall two columns to the right, bit 3 is 1. */ + | (pos % x == x - 2 ? 0 : (maz[pos + 2] ? 0 : 4)) + /* If there is a wall two colums to the left, bit 4 is 1. */ + | ((pos % x == 1 ) ? 0 : (maz[pos-2] ? 0 : 8)))) { + + /* Note if all bits are 0, d is false, we don't do this + while loop, we don't call ourselves again, so this branch + is done. */ + + /* I see what this loop does (more or less), but I don't know + _why_ it does it this way... I also haven't figured out exactly + which values of multiple will work and which won't. */ + do { + rnd = (rnd * mvals.multiple + mvals.offset); + i = 3 & (rnd / d); + if (++c > 100) { /* Break and try to salvage something */ + i=99; /* if it looks like we're going to be */ + break; /* here forever... */ + } + } while ( !(d & ( 1 << i) ) ); + /* ...While there's *not* a wall in direction i. */ + /* (stop looping when there is) */ + + switch (i) { /* This is simple enough. */ + case 0: /* Go in the direction we just figured . . . */ + j= -x; + break; + case 1: + j = x; + break; + case 2: + j=1; + break; + case 3: + j= -1; + break; + case 99: + return; /* Hey neat, broken mazes! */ + break; /* (Umm... Wow... Yeah, neat.) */ + default: + g_warning("maze: mazegen: Going in unknown direction.\n" + "i: %d, d: %d, seed: %d, mw: %d, mh: %d, mult: %d, offset: %d\n", + i, d,mvals.seed, x, y, mvals.multiple, mvals.offset); + break; + } + + /* And punch a hole there. */ + maz[pos + j] = 1; + + /* Now, start again just past where we punched the hole... */ + mazegen(pos + 2 * j, maz, x, y, rnd); + + } /* End while(d=...) Loop */ + + /* This routine is quite quick, even for rather large mazes. + For that reason, it doesn't need a progress bar. */ + return; +} + +/* Tileable mazes are my creation, based on the routine above. */ +void +mazegen_tileable(gint pos, gchar *maz, gint x, gint y, gint rnd) +{ + gchar d, i; + gint c=0, npos=2; + + /* Punch a hole here... */ + maz[pos] = IN; + + /* If there is a wall two rows above us, bit 1 is 1. */ + while((d= (maz[CELL_UP_TILEABLE(pos)] ? 0 : 1) + /* If there is a wall two rows below us, bit 2 is 1. */ + | (maz[CELL_DOWN_TILEABLE(pos)] ? 0 : 2) + /* If there is a wall two columns to the right, bit 3 is 1. */ + | (maz[CELL_RIGHT_TILEABLE(pos)] ? 0 : 4) + /* If there is a wall two colums to the left, bit 4 is 1. */ + | (maz[CELL_LEFT_TILEABLE(pos)] ? 0 : 8))) { + + /* Note if all bits are 0, d is false, we don't do this + while loop, we don't call ourselves again, so this branch + is done. */ + + /* I see what this loop does (more or less), but I don't know + _why_ it does it this way... I also haven't figured out exactly + which values of multiple will work and which won't. */ + do { + rnd = (rnd * mvals.multiple + mvals.offset); + i = 3 & (rnd / d); + if (++c > 100) { /* Break and try to salvage something */ + i=99; /* if it looks like we're going to be */ + break; /* here forever... */ + } + } while ( !(d & ( 1 << i) ) ); + /* ...While there's *not* a wall in direction i. */ + /* (stop looping when there is) */ + + switch (i) { /* This is simple enough. */ + case 0: /* Go in the direction we just figured . . . */ + maz[WALL_UP_TILEABLE(pos)]=IN; + npos = CELL_UP_TILEABLE(pos); + break; + case 1: + maz[WALL_DOWN_TILEABLE(pos)]=IN; + npos = CELL_DOWN_TILEABLE(pos); + break; + case 2: + maz[WALL_RIGHT_TILEABLE(pos)]=IN; + npos = CELL_RIGHT_TILEABLE(pos); + break; + case 3: + maz[WALL_LEFT_TILEABLE(pos)]=IN; + npos = CELL_LEFT_TILEABLE(pos); + break; + case 99: + return; /* Hey neat, broken mazes! */ + break; /* (Umm... Wow... Yeah, neat.) */ + default: + g_warning("maze: mazegen_tileable: Going in unknown direction.\n" + "i: %d, d: %d, seed: %d, mw: %d, mh: %d, mult: %d, offset: %d\n", + i, d,mvals.seed, x, y, mvals.multiple, mvals.offset); + break; + } + + /* Now, start again just past where we punched the hole... */ + mazegen_tileable(npos, maz, x, y, rnd); + + } /* End while(d=...) Loop */ + + return; +} + +#if 0 +static void +print_glist(gpointer data, gpointer user_data) +{ + g_print("%d ",(guint)data); +} +#endif + +/* This function (as well as prim_tileable) make use of the somewhat + unclean practice of storing ints as pointers. I've been informed + that this may cause problems with 64-bit stuff. However, hopefully + it will be okay, since the only values stored are positive. If it + does break, let me know, and I'll go cry in a corner for a while + before I get up the strength to re-code it. */ +void +prim(guint pos, gchar *maz, guint x, guint y, gint rnd) +{ + GSList *front_cells=NULL; + guint current; + gint up, down, left, right; /* Not unsigned, because macros return -1. */ + guint progress=0, max_progress; + char d, i; + guint c=0; + + gimp_progress_init ("Constructing maze using Prim's Algorithm..."); + + /* OUT is zero, so we should be already initalized. */ + + max_progress=x*y/4; + + /* Starting position has already been determined by the calling function. */ + + maz[pos]=IN; + + /* For now, repeating everything four times seems manageable. But when + Gimp is extended to drawings in n-dimensional space instead of 2D, + this will require a bit of a re-write. */ + /* Add frontier. */ + up=CELL_UP(pos); + down=CELL_DOWN(pos); + left=CELL_LEFT(pos); + right=CELL_RIGHT(pos); + + if (up >= 0) { + maz[up]=FRONTIER; + front_cells=g_slist_append(front_cells,(gpointer)up); + } + if (down >= 0) { + maz[up]=FRONTIER; + front_cells=g_slist_append(front_cells,(gpointer)down); + } + if (left >= 0) { + maz[up]=FRONTIER; + front_cells=g_slist_append(front_cells,(gpointer)left); + } + if (right >= 0) { + maz[up]=FRONTIER; + front_cells=g_slist_append(front_cells,(gpointer)right); + } + + /* While frontier is not empty do the following... */ + while(g_slist_length(front_cells) > 0) { + + /* Remove one cell at random from frontier and place it in IN. */ + current = rand() % g_slist_length(front_cells); + pos = (guint)g_slist_nth(front_cells,current)->data; + + front_cells=g_slist_remove(front_cells,(gpointer)pos); + maz[pos]=IN; + + /* If the cell has any neighbors in OUT, remove them from + OUT and place them in FRONTIER. */ + + up=CELL_UP(pos); + down=CELL_DOWN(pos); + left=CELL_LEFT(pos); + right=CELL_RIGHT(pos); + + d=0; + if (up>=0) { + switch (maz[up]) { + case OUT: + maz[up]=FRONTIER; + front_cells=g_slist_append(front_cells,(gpointer)up); + break; + case IN: + d=1; + break; + default: + ; + } + } + if (down>=0) { + switch (maz[down]) { + case OUT: + maz[down]=FRONTIER; + front_cells=g_slist_append(front_cells,(gpointer)down); + break; + case IN: + d=d|2; + break; + default: + ; + } + } + if (left>=0) { + switch (maz[left]) { + case OUT: + maz[left]=FRONTIER; + front_cells=g_slist_append(front_cells,(gpointer)left); + break; + case IN: + d=d|4; + break; + default: + ; + } + } + if (right>=0) { + switch (maz[right]) { + case OUT: + maz[right]=FRONTIER; + front_cells=g_slist_append(front_cells,(gpointer)right); + break; + case IN: + d=d|8; + break; + default: + ; + } + } + + /* The cell is guaranteed to have at least one neighbor in + IN (otherwise it would not have been in FRONTIER); pick + one such neighbor at random and connect it to the new + cell (ie knock out a wall). */ + + if (!d) { + g_warning("maze: prim: Lack of neighbors.\n" + "seed: %d, mw: %d, mh: %d, mult: %d, offset: %d\n", + mvals.seed, x, y, mvals.multiple, mvals.offset); + break; + } + + c=0; + do { + rnd = (rnd * mvals.multiple + mvals.offset); + i = 3 & (rnd / d); + if (++c > 100) { /* Break and try to salvage something */ + i=99; /* if it looks like we're going to be */ + break; /* here forever... */ + } + } while ( !(d & ( 1 << i) ) ); + + switch (i) { + case 0: + maz[WALL_UP(pos)]=1; + break; + case 1: + maz[WALL_DOWN(pos)]=1; + break; + case 2: + maz[WALL_LEFT(pos)]=1; + break; + case 3: + maz[WALL_RIGHT(pos)]=1; + break; + case 99: + break; + default: + g_warning("maze: prim: Going in unknown direction.\n" + "i: %d, d: %d, seed: %d, mw: %d, mh: %d, mult: %d, offset: %d\n", + i, d, mvals.seed, x, y, mvals.multiple, mvals.offset); + } + + if (progress++ % PRIMS_PROGRESS_UPDATE) + gimp_progress_update ((double) progress / (double) max_progress); + + } /* while front_cells */ + + g_slist_free(front_cells); +} /* prim */ + +void +prim_tileable(gchar *maz, guint x, guint y, gint rnd) +{ + GSList *front_cells=NULL; + guint current, pos; + guint up, down, left, right; + guint progress=0, max_progress; + char d, i; + guint c=0; + + gimp_progress_init ("Constructing tileable maze using Prim's Algorithm..."); + + /* OUT is zero, so we should be already initalized. */ + + max_progress=x*y/4; + + /* Pick someplace to start. */ + srand(rnd); + pos = x * 2 * (rand() % y / 2) + 2 * (rand() % x / 2); + + maz[pos]=IN; + + /* Add frontier. */ + up=CELL_UP_TILEABLE(pos); + down=CELL_DOWN_TILEABLE(pos); + left=CELL_LEFT_TILEABLE(pos); + right=CELL_RIGHT_TILEABLE(pos); + + maz[up]=maz[down]=maz[left]=maz[right]=FRONTIER; + + front_cells=g_slist_append(front_cells,(gpointer)up); + front_cells=g_slist_append(front_cells,(gpointer)down); + front_cells=g_slist_append(front_cells,(gpointer)left); + front_cells=g_slist_append(front_cells,(gpointer)right); + + /* While frontier is not empty do the following... */ + while(g_slist_length(front_cells) > 0) { + + /* Remove one cell at random from frontier and place it in IN. */ + current = rand() % g_slist_length(front_cells); + pos = (guint)g_slist_nth(front_cells,current)->data; + + front_cells=g_slist_remove(front_cells,(gpointer)pos); + maz[pos]=IN; + + /* If the cell has any neighbors in OUT, remove them from + OUT and place them in FRONTIER. */ + + up=CELL_UP_TILEABLE(pos); + down=CELL_DOWN_TILEABLE(pos); + left=CELL_LEFT_TILEABLE(pos); + right=CELL_RIGHT_TILEABLE(pos); + + d=0; + switch (maz[up]) { + case OUT: + maz[up]=FRONTIER; + front_cells=g_slist_append(front_cells,(gpointer)up); + break; + case IN: + d=1; + break; + default: + ; + } + switch (maz[down]) { + case OUT: + maz[down]=FRONTIER; + front_cells=g_slist_append(front_cells,(gpointer)down); + break; + case IN: + d=d|2; + break; + default: + ; + } + switch (maz[left]) { + case OUT: + maz[left]=FRONTIER; + front_cells=g_slist_append(front_cells,(gpointer)left); + break; + case IN: + d=d|4; + break; + default: + ; + } + switch (maz[right]) { + case OUT: + maz[right]=FRONTIER; + front_cells=g_slist_append(front_cells,(gpointer)right); + break; + case IN: + d=d|8; + break; + default: + ; + } + + /* The cell is guaranteed to have at least one neighbor in + IN (otherwise it would not have been in FRONTIER); pick + one such neighbor at random and connect it to the new + cell (ie knock out a wall). */ + + if (!d) { + g_warning("maze: prim's tileable: Lack of neighbors.\n" + "seed: %d, mw: %d, mh: %d, mult: %d, offset: %d\n", + mvals.seed, x, y, mvals.multiple, mvals.offset); + break; + } + + c=0; + do { + rnd = (rnd * mvals.multiple + mvals.offset); + i = 3 & (rnd / d); + if (++c > 100) { /* Break and try to salvage something */ + i=99; /* if it looks like we're going to be */ + break; /* here forever... */ + } + } while ( !(d & ( 1 << i) ) ); + + switch (i) { + case 0: + maz[WALL_UP_TILEABLE(pos)]=1; + break; + case 1: + maz[WALL_DOWN_TILEABLE(pos)]=1; + break; + case 2: + maz[WALL_LEFT_TILEABLE(pos)]=1; + break; + case 3: + maz[WALL_RIGHT_TILEABLE(pos)]=1; + break; + case 99: + break; + default: + g_warning("maze: prim's tileable: Going in unknown direction.\n" + "i: %d, d: %d, seed: %d, mw: %d, mh: %d, mult: %d, offset: %d\n", + i, d, mvals.seed, x, y, mvals.multiple, mvals.offset); + } + + if (progress++ % PRIMS_PROGRESS_UPDATE) + gimp_progress_update ((double) progress / (double) max_progress); + + } /* while front_cells */ + + g_slist_free(front_cells); +} diff --git a/plug-ins/maze/handy.c b/plug-ins/maze/handy.c new file mode 100644 index 0000000000..ac7b27216f --- /dev/null +++ b/plug-ins/maze/handy.c @@ -0,0 +1,228 @@ +/* handy.c + * These routines are useful for working with the GIMP and need not be + * specific to plug-in-maze. + * + * Kevin Turner + * http://www.poboxes.com/kevint/gimp/maze.html + */ + +/* + * 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. + * + */ + +#include "libgimp/gimp.h" + +/* get_colors Returns the current foreground and background colors in + nice little arrays. It works nicely for RGB and grayscale images, + however handling of indexed images is somewhat broken. Patches + appreciated. */ +void get_colors (GDrawable * drawable, + guint8 *fg, + guint8 *bg); + +/* drawbox draws a solid colored box in a GPixelRgn, hopefully fairly + quickly. See comments below. */ +void drawbox (GPixelRgn *dest_rgn, + guint x, + guint y, + guint w, + guint h, + guint8 clr[4]); + + +void +get_colors (GDrawable *drawable, guint8 *fg, guint8 *bg) +{ + GParam *return_vals; + gint nreturn_vals; + + switch ( gimp_drawable_type (drawable->id) ) + { + case RGBA_IMAGE: /* ASSUMPTION: Assuming the user wants entire */ + fg[3] = 255; /* area to be fully opaque. */ + bg[3] = 255; + case RGB_IMAGE: + + return_vals = gimp_run_procedure ("gimp_palette_get_foreground", + &nreturn_vals, + PARAM_END); + + if (return_vals[0].data.d_status == STATUS_SUCCESS) + { + fg[0] = return_vals[1].data.d_color.red; + fg[1] = return_vals[1].data.d_color.green; + fg[2] = return_vals[1].data.d_color.blue; + } + else + { + fg[0] = 255; + fg[1] = 255; + fg[2] = 255; + } + return_vals = gimp_run_procedure ("gimp_palette_get_background", + &nreturn_vals, + PARAM_END); + + if (return_vals[0].data.d_status == STATUS_SUCCESS) + { + bg[0] = return_vals[1].data.d_color.red; + bg[1] = return_vals[1].data.d_color.green; + bg[2] = return_vals[1].data.d_color.blue; + } + else + { + bg[0] = 0; + bg[1] = 0; + bg[2] = 0; + } + break; + case GRAYA_IMAGE: /* and again */ + fg[1] = 255; + bg[1] = 255; + case GRAY_IMAGE: + + return_vals = gimp_run_procedure ("gimp_palette_get_foreground", + &nreturn_vals, + PARAM_END); + + if (return_vals[0].data.d_status == STATUS_SUCCESS) + { + fg[0] = 0.30 * return_vals[1].data.d_color.red + + 0.59 * return_vals[1].data.d_color.green + + 0.11 * return_vals[1].data.d_color.blue; + } + else + { + fg[0] = 255; + } + + return_vals = gimp_run_procedure ("gimp_palette_get_background", + &nreturn_vals, + PARAM_END); + + if (return_vals[0].data.d_status == STATUS_SUCCESS) + { + bg[0] = 0.30 * return_vals[1].data.d_color.red + + 0.59 * return_vals[1].data.d_color.green + + 0.11 * return_vals[1].data.d_color.blue; + } + else + { + bg[0] = 0; + } + + break; + case INDEXEDA_IMAGE: + case INDEXED_IMAGE: /* FIXME: Should use current fg/bg colors. */ + g_warning("maze: get_colors: Indexed image. Using colors 15 and 0.\n"); + fg[0] = 15; /* As a plugin, I protest. *I* shouldn't be the */ + bg[0] = 0; /* one who has to deal with this colormapcrap. */ + break; + default: + break; + } +} + +/* Draws a solid color box in a GPixelRgn. */ +/* Optimization assumptions: + * (Or, "Why Maze is Faster Than Checkerboard.") + * + * Assuming calling memcpy is faster than using loops. + * Row buffers are nice... + * + * Assume allocating memory for row buffers takes a significant amount + * of time. Assume drawbox will be called many times. + * Only allocate memory once. + * + * Do not assume the row buffer will always be the same size. Allow + * for reallocating to make it bigger if needed. However, I don't see + * reason to bother ever shrinking it again. + * (Under further investigation, assuming the row buffer never grows + * may be a safe assumption in this case.) + * + * Also assume that the program calling drawbox is short-lived, so + * memory leaks aren't of particular concern-- the memory allocated to + * the row buffer is never set free. + */ + +/* Further optimizations that could be made... + * Currently, the row buffer is re-filled with every call. However, + * plug-ins such as maze and checkerboard only use two colors, and + * for the most part, have rows of the same size with every call. + * We could keep a row of each color on hand so we wouldn't have to + * re-fill it every time... */ + +void +drawbox( GPixelRgn *dest_rgn, + guint x, guint y, guint w, guint h, + guint8 clr[4]) +{ + guint xx, yy, y_max, x_min /*, x_max */; + static guint8 *rowbuf; + guint rowsize; + static guint high_size=0; + + /* The maximum [xy] value is that of the far end of the box, or + * the edge of the region, whichever comes first. */ + + y_max = dest_rgn->rowstride * MIN(dest_rgn->h, (y + h)); +/* x_max = dest_rgn->bpp * MIN(dest_rgn->w, (x + w)); */ + + x_min = x * dest_rgn->bpp; + + /* rowsize = x_max - x_min */ + rowsize = dest_rgn->bpp * MIN(dest_rgn->w, (x + w)) - x_min; + + /* Does the row buffer need to be (re)allocated? */ + if (high_size == 0) { + rowbuf = g_new(guint8, rowsize); + } else if (rowsize > high_size) { + g_realloc(rowbuf, rowsize * sizeof(guint8) ); + } + + high_size = MAX(high_size, rowsize); + + /* Fill the row buffer with the color. */ + for (xx= 0; + xx < rowsize; + xx+= dest_rgn->bpp) { + memcpy (&rowbuf[xx], clr, dest_rgn->bpp); + } /* next xx */ + + /* Fill in the box in the region with rows... */ + for (yy = dest_rgn->rowstride * y; + yy < y_max; + yy += dest_rgn->rowstride ) { + memcpy (&dest_rgn->data[yy+x_min], rowbuf, rowsize); + } /* next yy */ +} + +/* Alternate ways of doing things if you don't like memcpy. */ +#if 0 + for (xx= x * dest_rgn->bpp; + xx < bar; + xx+= dest_rgn->bpp) { +#if 0 + for (bp=0; bp < dest_rgn->bpp; bp++) { + dest_rgn->data[yy+xx+bp]=clr[bp]; + } /* next bp */ +#else + memcpy (&dest_rgn->data[yy+xx], clr, dest_rgn->bpp); +#endif + } /* next xx */ + } /* next yy */ +} +#endif diff --git a/plug-ins/maze/maze.c b/plug-ins/maze/maze.c index 9e774afe9d..edbc2e51f9 100644 --- a/plug-ins/maze/maze.c +++ b/plug-ins/maze/maze.c @@ -1,4 +1,4 @@ -/* maze.c, version 0.6.2, March 7, 1998. +/* maze.c * This is a plug-in for the GIMP. * It draws mazes... * @@ -6,66 +6,17 @@ * Kevin Turner * http://www.poboxes.com/kevint/gimp/maze.html * - * mazegen code from rec.games.programmer's maze-faq: - * * maz.c - generate a maze - * * - * * algorithm posted to rec.games.programmer by jallen@ic.sunysb.edu - * * program cleaned and reorganized by mzraly@ldbvax.dnet.lotus.com - * * - * * don't make people pay for this, or I'll jump up and down and - * * yell and scream and embarass you in front of your friends... - * * Code generously borrowed from assorted GIMP plugins * and used as a template to get me started on this one. :) * - * Revision history: - * 0.6.2 - drawbox rewritten with memcpy and a row buffer. By the way, Maze is now - * faster than Checkerboard. - * - Added Help button using extension_web_browser. - * - Added DIVBOX_LOOKS_LIKE_SPINBUTTON flag. Doesn't look too hot, set to - * FALSE by default. - * 0.6.1 - Made use-time-for-random-seed a toggle button that remembers - * its state, and moved seed to the second notebook page. - * 0.6.0 - Width and height are now seperate options. - * ^^ Note this changed the PDB interface. ^^ - * - * - Added interface for selecting sizes by "divisions". - * - Added "Time" button for random seed. - * - Turned out that GParam shouldn't have been "fixed". - * 0.5.0 - Added the long-awaited "tileable" option. - * Required a change to PDB parameters. - * - fixed some stuff with GParam values in run(); - * 0.4.2 - Applied Adrian Likins' patch to fix non-interactive stuff. - * - -ansi and -pedantic-errors clean. Woo-hoo? - * 0.4.1 - get_colors() now works properly for grayscale images. - * I'd still like it to do indexed too, but I don't know - * if that's worth breaking a sweat over. - * - We're -Wall clean now. Woohoo! - * 0.4.0 - Code for the painting of the maze has been almost completely rebuilt. - * Hopefully it's a more sane and speedier approach. - * Utilizes a new function, drawbox, which colors a solid rectangle. - * (Good excercise, in any case.) - * - Order of paramaters changed, defaults are used if not given. - * - Discovery made that that was an utterly useless thing to do. - * 0.3.0 - Maze is centered with dead space around outside - * - Width slider works... And does stuff! - * - Allows partial mazes to be generated with "broken" multiple - * and offset values. - * 0.1.99 - Has dialog box with seed, multiple, and offset. - * 0.1.0 - First release. It works! :) - * * TO DO: - * Rework the divboxes to be more like spinbuttons. + * maze_face.c: Rework the divboxes to be more like spinbuttons. * - * Add an option to kill the outer border. + * Maybe add an option to kill the outer border. * * Fix that stray line down there between maze wall and dead space border... * - * Make get_colors() work with indexed. * HELP! * - * - * Also someday: - * Maybe make it work with irregularly shaped selections? - * Add different generation algorythms. + * handy.c: Make get_colors() work with indexed. * HELP! * * */ @@ -82,13 +33,14 @@ * * 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. + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #ifdef MAZE_DEBUG #include #include +#include #endif #include /* For random seeding */ @@ -103,21 +55,37 @@ static void run (gchar *name, gint *nreturn_vals, GParam **return_vals); static void maze (GDrawable * drawable); -static gint mazegen(gint pos, - gchar *maz, + +static void mask_maze(gint32 selection_ID, guchar *maz, guint mw, guint mh, + gint x1, gint x2, gint y1, gint y2, gint deadx, gint deady); + +/* In algorithms.c */ +extern void mazegen(gint pos, + guchar *maz, gint x, gint y, gint rnd); -static gint mazegen_tileable(gint pos, - gchar *maz, +extern void mazegen_tileable(gint pos, + guchar *maz, gint x, gint y, gint rnd); -static void get_colors (GDrawable * drawable, +extern void prim(guint pos, + guchar *maz, + guint x, + guint y, + gint rnd); +extern void prim_tileable(guchar *maz, + guint x, + guint y, + gint rnd); + +/* In handy.c */ +extern void get_colors (GDrawable * drawable, guint8 *fg, guint8 *bg); -static void drawbox (GPixelRgn *dest_rgn, +extern void drawbox (GPixelRgn *dest_rgn, guint x, guint y, guint w, @@ -141,6 +109,7 @@ MazeValues mvals = FALSE, /* Tileable? */ 57, /* multiple * These two had "Experiment with this?" comments */ 1, /* offset * in the maz.c source, so, lets expiriment. :) */ + DEPTH_FIRST, /* Algorithm */ /* Interface options */ TRUE /* Time seed? */ }; @@ -155,26 +124,28 @@ query () static GParamDef args[] = { { PARAM_INT32, "run_mode", "Interactive, non-interactive" }, - { PARAM_IMAGE, "image", "Input image (unused)" }, - { PARAM_DRAWABLE, "drawable", "Input drawable" }, + { PARAM_IMAGE, "image_ID", "(unused)" }, + { PARAM_DRAWABLE, "drawable_ID", "ID of drawable" }, /* If we did have parameters, these be them: */ - { PARAM_INT32, "mazep_width", "Width of the passages" }, - { PARAM_INT32, "mazep_height", "Height of the passages"}, - { PARAM_INT8, "maze_tile", "Tileable maze?"}, - { PARAM_INT32, "maze_rseed", "Random Seed"}, - { PARAM_INT32, "maze_multiple", "Multiple (use 57)" }, - { PARAM_INT32, "maze_offset", "Offset (use 1)" } + { PARAM_INT16, "width", "Width of the passages" }, + { PARAM_INT16, "height", "Height of the passages"}, + { PARAM_INT8, "tileable", "Tileable maze?"}, + { PARAM_INT8, "algorithm", "Generation algorithm" + "(0=DEPTH FIRST, 1=PRIM'S ALGORITHM)" }, + { PARAM_INT32, "seed", "Random Seed"}, + { PARAM_INT16, "multiple", "Multiple (use 57)" }, + { PARAM_INT16, "offset", "Offset (use 1)" } }; static GParamDef *return_vals = NULL; static int nargs = sizeof (args) / sizeof (args[0]); static int nreturn_vals = 0; gimp_install_procedure ("plug_in_maze", - "Generates a maze.", - "Generates a maze using the depth-first search method.", + "Draws a maze.", + "Generates a maze using either the depth-first search method or Prim's algorithm. Can make tileable mazes too. See " MAZE_URL " for more help.", "Kevin Turner ", "Kevin Turner", - "1997", + "1997, 1998", "/Filters/Render/Maze", "RGB*, GRAY*, INDEXED*", PROC_PLUG_IN, @@ -226,18 +197,19 @@ run (gchar *name, break; case RUN_NONINTERACTIVE: - if (nparams != 9) + if (nparams != 10) { status = STATUS_CALLING_ERROR; } if (status == STATUS_SUCCESS) { - mvals.width = (gint) param[3].data.d_int32; - mvals.height = (gint) param[4].data.d_int32; - mvals.seed = (gint) param[5].data.d_int32; - mvals.tile = (gboolean) param[6].data.d_int32; - mvals.multiple = (gint) param[7].data.d_int32; - mvals.offset = (gint) param[8].data.d_int32; + mvals.width = (gint16) param[3].data.d_int32; + mvals.height = (gint16) param[4].data.d_int32; + mvals.tile = (gint8) param[5].data.d_int32; + mvals.algorithm = (gint8) param[6].data.d_int32; + mvals.seed = (gint32) param[7].data.d_int32; + mvals.multiple = (gint16) param[8].data.d_int32; + mvals.offset = (gint16) param[9].data.d_int32; } break; case RUN_WITH_LAST_VALS: @@ -251,7 +223,6 @@ run (gchar *name, /* color, gray, or indexed... hmm, miss anything? ;) */ if (gimp_drawable_color (drawable->id) || gimp_drawable_gray (drawable->id) || gimp_drawable_indexed (drawable->id)) { - gimp_progress_init ("Drawing Maze..."); maze (drawable); @@ -270,34 +241,56 @@ run (gchar *name, gimp_drawable_detach (drawable); } +#ifdef MAZE_DEBUG +void +maze_dump(guchar *maz, gint mw, gint mh) +{ + short xx, yy; + int foo=0; + + for(yy=0;yyid, &x1, &y1, &x2, &y2); + active_selection = gimp_drawable_mask_bounds (drawable->id, &x1, &y1, &x2, &y2); - /* Initialize pixel region (?) */ - gimp_pixel_rgn_init (&dest_rgn, drawable, x1, y1, (x2 - x1), (y2 - y1), TRUE, TRUE); + /***************** Maze Stuff Happens Here ***************/ - progress = 0; - max_progress = (x2 - x1) * (y2 - y1); - - /* Get the foreground and background colors */ - get_colors(drawable,fg,bg); - - /* Maze Stuff Happens Here */ mw = (x2-x1) / mvals.width; mh = (y2-y1) / mvals.height; @@ -314,7 +307,7 @@ maze( GDrawable * drawable) deadx = ((x2-x1) - mw * mvals.width)/2; deady = ((y2-y1) - mh * mvals.height)/2; - maz = g_malloc0(mw * mh); + maz = g_new0(guchar, mw * mh); #ifdef MAZE_DEBUG printf("x: %d\ty: %d\nmw: %d\tmh: %d\ndx: %d\tdy: %d\nwidth:%d\theight: %d\n", @@ -324,13 +317,75 @@ maze( GDrawable * drawable) if (mvals.timeseed) mvals.seed = time(NULL); - if (mvals.tile) { - (void) mazegen_tileable((mw+1), maz, mw, mh, mvals.seed); - } else { - (void) mazegen((mw+1), maz, mw, mh, mvals.seed); - /* (void) mazegen(((x2-x1)+1), maz, (x2-x1), (y2-y1), rnd); */ + /* Sanity check: */ + switch (mvals.algorithm) { + case DEPTH_FIRST: + break; + case PRIMS_ALGORITHM: + break; + default: + g_warning("maze: Invalid algorithm choice %d", mvals.algorithm); } - /* It's done happening. Now go through and color dem pixels... */ + + if (mvals.tile) { + switch (mvals.algorithm) { + case DEPTH_FIRST: + mazegen_tileable(0, maz, mw, mh, mvals.seed); + break; + case PRIMS_ALGORITHM: + prim_tileable(maz, mw, mh, mvals.seed); + break; + default: + ; + } + } else { /* not tileable */ + if (active_selection) { /* Mask and draw mazes until there's no + * more room left. */ + mask_maze(drawable->id, + maz, mw, mh, x1, x2, y1, y2, deadx, deady); + for(maz_yy=mw; maz_yy < (mh*mw); maz_yy += 2*mw) { + for(maz_xx=1; maz_xx < mw; maz_xx += 2) { + if(maz[maz_yy+maz_xx] == 0) { + switch(mvals.algorithm) { + case DEPTH_FIRST: + mazegen(maz_yy+maz_xx, maz, mw, mh, mvals.seed); + break; + case PRIMS_ALGORITHM: + prim(maz_yy+maz_xx, maz, mw, mh, mvals.seed); + break; + default: + ; + } /* switch */ + } /* if maz[] == 0 */ + } /* next maz_xx */ + } /* next maz_yy */ + } else { /* No active selection. */ + pos=mw+1; + switch(mvals.algorithm) { + case DEPTH_FIRST: + mazegen(pos, maz, mw, mh, mvals.seed); + break; + case PRIMS_ALGORITHM: + prim(pos, maz, mw, mh, mvals.seed); + break; + default: + ; + } /* switch */ + } /* no active selection */ + } /* not tileable */ + + /************** Begin Drawing *********************/ + + /* Initialize pixel region (?) */ + gimp_pixel_rgn_init (&dest_rgn, drawable, x1, y1, (x2 - x1), (y2 - y1), TRUE, TRUE); + + progress = 0; + max_progress = (x2 - x1) * (y2 - y1); + + /* Get the foreground and background colors */ + get_colors(drawable,fg,bg); + + gimp_progress_init ("Drawing Maze..."); for (pr = gimp_pixel_rgns_register (1, &dest_rgn); pr != NULL; @@ -345,375 +400,276 @@ maze( GDrawable * drawable) dx = mvals.width - (x % mvals.width); dy = mvals.height - (y % mvals.height); - foo = x/mvals.width; - bar = mw * (y/mvals.height); + maz_x = x/mvals.width; + maz_row = mw * (y/mvals.height); /* Draws the upper-left [split] box */ drawbox(&dest_rgn,0,0,dx,dy, - maz[foo+bar] ? fg : bg); + (maz[maz_row+maz_x]==IN) ? fg : bg); - baz=foo+1; + maz_xx=maz_x+1; /* Draw the top row [split] boxes */ for(xx=dx; xx < dest_rgn.w; xx+=mvals.width) { drawbox(&dest_rgn,xx,0,mvals.width,dy, - maz[bar + baz++] ? fg : bg ); } + (maz[maz_row + maz_xx++]==IN) ? fg : bg ); } - baz=bar+mw; + maz_yy=maz_row+mw; /* Left column */ for(yy=dy; yy < dest_rgn.h; yy+=mvals.height) { drawbox(&dest_rgn,0,yy,dx,mvals.height, - maz[foo + baz] ? fg : bg ); - baz += mw; + (maz[maz_yy + maz_x]==IN) ? fg : bg ); + maz_yy += mw; } - foo++; + maz_x++; /* Everything else */ for(yy=dy; yy < dest_rgn.h; yy+=mvals.height) { - baz = foo; bar+=mw; + maz_xx = maz_x; maz_row+=mw; for(xx=dx; xx < dest_rgn.w; xx+=mvals.width) { drawbox(&dest_rgn,xx,yy,mvals.width,mvals.height, - maz[bar + baz++] ? fg : bg ); } + (maz[maz_row + maz_xx++]==IN) ? fg : bg ); } } progress += dest_rgn.w * dest_rgn.h; gimp_progress_update ((double) progress / (double) max_progress); - /* Note the progess indicator doesn't indicate how much of the maze - has been built... It just indicates how much has been drawn - *after* building... Thing is, that's what takes longer. */ + /* Indicate progress in drawing. */ } gimp_drawable_flush (drawable); gimp_drawable_merge_shadow (drawable->id, TRUE); gimp_drawable_update (drawable->id, x1, y1, (x2 - x1), (y2 - y1)); } -/* Draws a solid color box in a GPixelRgn. */ -/* Optimization assumptions: - * (Or, "Why Maze is Faster Than Checkerboard.") +/* Shaped mazes: */ +/* With + * Depth first: Nonzero cells will not be connected to or visited. + * Prim's Algorithm: + * Cells that are not IN will not be connected to. + * Cells that are not OUT will not be converted to FRONTIER. * - * Assuming calling memcpy is faster than using loops. - * Row buffers are nice... + * So we'll put unavailable cells in a non-zero non-in non-out class + * called MASKED. + */ + +/* But first... A little discussion about cells. */ + +/* In the eyes of the generation algorithms, the world is made up of + * two sorts of things: Cells, and the walls between them. Walls can + * be knocked out, and then you have a passage between cells. The + * drawing routine has a simpler view of life... Everything is a + * pixel. Or a block of pixels. It makes no distinction between + * passages, walls, and cells. * - * Assume allocating memory for row buffers takes a significant amount - * of time. Assume drawbox will be called many times. - * Only allocate memory once. + * We may also make the distinction between two different types of + * passages: horizontal and vertical. With that in mind, a + * part of the world looks something like this: + * + * @-@-@-@- Where @ is a cell, | is a vertical passage, and - is a + * | | | | horizontal passsage. + * @-@-@-@- + * | | | | Remember, the maze generation routines will not rest + * until the maze is full, that is, every cell is connected + * to another. Already, we can determine a few things about the final + * maze. We know which blocks will be cells, which blocks may become + * passages (and we know what sort), and we also notice that there are + * some blocks that will never be either cells or passages. * - * Do not assume the row buffer will always be the same size. Allow - * for reallocating to make it bigger if needed. However, I don't see - * reason to bother ever shrinking it again. - * (Under further investigation, assuming the row buffer never grows - * may be a safe assumption in this case.) - * - * Also assume that the program calling drawbox is short-lived, so - * memory leaks aren't of particular concern-- the memory allocated to - * the row buffer is never set free. + * Now, back to our masking routine... To save a little time, lets + * just take sample points from the block. We'll sample a point from + * the top and the bottom of vertical passages, left/right for + * horizontal, and, hmm, left/right/top/bottom for cells. And of + * course, we needn't concern ourselves with the others. We could + * also sample the midpoint of each... + * Then what we'll do is see if the average is higher than some magic + * threshold number, and if so, we let maze happen there. Otherwise + * we mask it out. */ -/* Further optimizations that could be made... - * Currently, the row buffer is re-filled with every call. However, - * plug-ins such as maze and checkerboard only use two colors, and - * for the most part, have rows of the same size with every call. - * We could keep a row of each color on hand so we wouldn't have to - * re-fill it every time... */ - - -static void -drawbox( GPixelRgn *dest_rgn, - guint x, guint y, guint w, guint h, - guint8 clr[4]) +/* And, uh, that's on the TODO list. Looks like I spent so much time + * writing comments I haven't left enough to implement the code. :) + * Right now we only sample one point. */ +static void +mask_maze(gint32 drawable_ID, guchar *maz, guint mw, guint mh, + gint x1, gint x2, gint y1, gint y2, gint deadx, gint deady) { - guint xx, yy, y_max, x_min /*, x_max */; - static guint8 *rowbuf; - guint rowsize; - static guint high_size=0; - - /* The maximum [xy] value is that of the far end of the box, or - * the edge of the region, whichever comes first. */ - - y_max = dest_rgn->rowstride * MIN(dest_rgn->h, (y + h)); -/* x_max = dest_rgn->bpp * MIN(dest_rgn->w, (x + w)); */ - - x_min = x * dest_rgn->bpp; - - /* rowsize = x_max - x_min */ - rowsize = dest_rgn->bpp * MIN(dest_rgn->w, (x + w)) - x_min; - - /* Does the row buffer need to be (re)allocated? */ - if (high_size == 0) { - rowbuf = g_new(guint8, rowsize); - } else if (rowsize > high_size) { - g_realloc(rowbuf, rowsize * sizeof(guint8) ); - } - - high_size = MAX(high_size, rowsize); - - /* Fill the row buffer with the color. */ - for (xx= 0; - xx < rowsize; - xx+= dest_rgn->bpp) { - memcpy (&rowbuf[xx], clr, dest_rgn->bpp); - } /* next xx */ - - /* Fill in the box in the region with rows... */ - for (yy = dest_rgn->rowstride * y; - yy < y_max; - yy += dest_rgn->rowstride ) { - memcpy (&dest_rgn->data[yy+x_min], rowbuf, rowsize); - } /* next yy */ -} + gint32 selection_ID; + GPixelRgn sel_rgn; + gint xx0=0, yy0=0, xoff, yoff; + guint xx=0, yy=0; + guint foo=0; -/* The old drawbox code, preserved here 'just in case' something - doesn't go right. */ + gint cur_row, cur_col; + gint x1half, x2half, y1half, y2half; + guchar *linebuf; + if ((selection_ID = + gimp_image_get_selection(gimp_drawable_image_id(drawable_ID))) + == -1) + return; + + gimp_pixel_rgn_init(&sel_rgn, gimp_drawable_get(selection_ID), + x1, y1, (x2-x1), (y2-y1), + FALSE, FALSE); + gimp_drawable_offsets(drawable_ID, &xoff, &yoff); + + /* Special cases: If mw or mh < 3 */ + /* FIXME (Currently works, but inefficiently.) */ + + /* mw && mh => 3 */ + + linebuf = g_new(guchar, sel_rgn.w * sel_rgn.bpp); + + xx0 = x1 + deadx + mvals.width + xoff; + yy0 = y1 + deady + mvals.height + yoff; + + x1half = mvals.width/2; + x2half = mvals.width - 1; + + y1half = mvals.height/2; + y2half = mvals.height - 1; + + /* Here, yy is with respect to the drawable (or something), + whereas xx is with respect to the row buffer. */ + + yy=yy0 + y1half; + for(cur_row=1; cur_row < mh; cur_row += 2) { + + gimp_pixel_rgn_get_row(&sel_rgn, linebuf, x1+xoff, yy, (x2-x1)); + + cur_col=1; xx=mvals.width; + while(cur_col < mw) { + + /* Cell: */ + maz[cur_row * mw + cur_col] = + (linebuf[xx] + linebuf[xx + x1half] + linebuf[xx+x2half]) / 5; + + cur_col += 1; + xx += mvals.width; + + /* Passage: */ + if (cur_col < mw) + maz[cur_row * mw + cur_col] = + (linebuf[xx] + linebuf[xx + x1half] + linebuf[xx+x2half]) / 3; + + cur_col += 1; + xx += mvals.width; + + } /* next col */ + + yy += 2 * mvals.height; + + } /* next cur_row += 2 */ + + /* Done doing horizontal scans, change this from a row buffer to + a column buffer. */ + g_free(linebuf); + linebuf = g_new(guchar, sel_rgn.h * sel_rgn.bpp); + + /* Now xx is with respect to the drawable (or whatever), + and yy is with respect to the row buffer. */ + + xx=xx0 + x1half; + for(cur_col=1; cur_col < mw; cur_col += 2) { + + gimp_pixel_rgn_get_col(&sel_rgn, linebuf, xx, y1, (y2-y1)); + + cur_row=1; yy=mvals.height; + while(cur_row < mh) { + + /* Cell: */ + maz[cur_row * mw + cur_col] += + (linebuf[yy] + linebuf[yy+y2half]) / 5; + + cur_row += 1; + yy += mvals.height; + + /* Passage: */ + if (cur_row < mh) + maz[cur_row * mw + cur_col] = + (linebuf[yy] + linebuf[yy + y1half] + linebuf[yy+y2half]) / 3; + + cur_row += 1; + yy += mvals.height; + } /* next cur_row */ + + xx += 2 * mvals.width; + + } /* next cur_col */ + + g_free(linebuf); + + /* Do the alpha -> masked conversion. */ + + for(yy=0;yybpp; - xx < bar; - xx+= dest_rgn->bpp) { -#if 0 - for (bp=0; bp < dest_rgn->bpp; bp++) { - dest_rgn->data[yy+xx+bp]=clr[bp]; - } /* next bp */ -#else - memcpy (&dest_rgn->data[yy+xx], clr, dest_rgn->bpp); -#endif - } /* next xx */ - } /* next yy */ -} -#endif -/* The Incredible Recursive Maze Generation Routine */ -/* Ripped from rec.programmers.games maze-faq */ -/* Modified and commented by me, Kevin Turner. */ -gint mazegen(pos, maz, x, y, rnd) -gint pos, x, y, rnd; -gchar *maz; -{ - gchar d, i; - gint c=0, j=1; + /* Tiles make my life decidedly difficult here. There are too + * many special cases... "What if a tile starts less/more than + * halfway through a block? What if we get a narrow edge-tile + * that..." etc, etc. I shall investigate other options. + * Possibly a row buffer, or can we use something other than this + * black-magic gimp_pixel_rgns_register call to get tiles of + * different sizes? Now that'd be nice... */ - /* Punch a hole here... */ - maz[pos] = 1; + for (pr = gimp_pixel_rgns_register (1, &sel_rgn); + pr != NULL; + pr = gimp_pixel_rgns_process (pr)) { - /* If there is a wall two rows above us, bit 1 is 1. */ - while((d= (pos <= (x * 2) ? 0 : (maz[pos - x - x ] ? 0 : 1)) - /* If there is a wall two rows below us, bit 2 is 1. */ - | (pos >= x * (y - 2) ? 0 : (maz[pos + x + x] ? 0 : 2)) - /* If there is a wall two columns to the right, bit 3 is 1. */ - | (pos % x == x - 2 ? 0 : (maz[pos + 2] ? 0 : 4)) - /* If there is a wall two colums to the left, bit 4 is 1. */ - | ((pos % x == 1 ) ? 0 : (maz[pos-2] ? 0 : 8)))) { + /* This gives us coordinates relative to the starting point + * of the maze grid. Negative values happen here if there + * is dead space. */ + x = sel_rgn.x - x1 - deadx; + y = sel_rgn.y - y1 - deady; - /* Note if all bits are 0, d is false, we don't do this - while loop, we don't call ourselves again, so this branch - is done. */ + /* These coordinates are relative to the current tile. */ + /* This starts us off at the first block boundary in the + * tile. */ - /* I see what this loop does (more or less), but I don't know - _why_ it does it this way... I also haven't figured out exactly - which values of multiple will work and which won't. */ - do { - rnd = (rnd * mvals.multiple + mvals.offset); - i = 3 & (rnd / d); - if (++c > 100) { /* Break and try to salvage something */ - i=99; /* if it looks like we're going to be */ - break; /* here forever... */ - } - } while ( !(d & ( 1 << i) ) ); - /* ...While there's *not* a wall in direction i. */ - /* (stop looping when there is) */ +/* e.g. with x=16 and width=10. + * 16 % 10 = 6 + * 10 - 6 = 4 - switch (i) { /* This is simple enough. */ - case 0: /* Go in the direction we just figured . . . */ - j= -x; - break; - case 1: - j = x; - break; - case 2: - j=1; - break; - case 3: - j= -1; - break; - case 99: - return 1; /* Hey neat, broken mazes! */ - break; /* (Umm... Wow... Yeah, neat.) */ - default: - break; - } + x: 6789!123456789!123456789!12 + ....|.........|.........|.. + xx: 0123456789!123456789!123456 - /* And punch a hole there. */ - maz[pos + j] = 1; + So to start on the boundary, begin at 4. - /* Now, start again just past where we punched the hole... */ - mazegen(pos + 2 * j, maz, x, y, rnd); + For the case x=0, 10-0=10. So xx0 will always between 1 and width. */ - } /* End while(d=...) Loop */ + xx0 = mvals.width - (x % mvals.width); + yy0 = mvals.height - (y % mvals.height); - return 0; -} + /* Find the corresponding row and column in the maze. */ + maz_x = (x+xx0)/mvals.width; + maz_row = mw * ((y + yy0)/mvals.height); + + for (yy=yy0*sel_rgn.rowstride; + yy < sel_rgn.h*sel_rgn.rowstride; + yy+=(mvals.height * sel_rgn.rowstride)) { + maz_xx = maz_x; + for(xx=xx0*sel_rgn.bpp; + xx < sel_rgn.w; + xx+=mvals.width*sel_rgn.bpp) { + if (sel_rgn.data[yy+xx] < MAZE_ALPHA_THRESHOLD) + maz[maz_row+maz_xx]=MASKED; + maz_xx++; + } /* next xx */ + maz_row+=mw; + } /* next yy */ -#define ABSMOD(A,B) ( ((A) < 0) ? (((B) + (A)) % (B)) : ((A) % (B)) ) - -/* Tileable mazes are my creation, based on the routine above. */ -static gint mazegen_tileable(gint pos, gchar *maz, gint x, gint y, gint rnd) -{ - gchar d, i; - gint c=0, j=1, npos=2; - - /* Punch a hole here... */ - maz[pos] = 1; - - /* If there is a wall two rows above us, bit 1 is 1. */ - while((d= (pos < (x*2) ? (maz[x*(y-2)+pos] ? 0 : 1) : (maz[pos - x - x ] ? 0 : 1)) - /* If there is a wall two rows below us, bit 2 is 1. */ - | (pos >= x * (y-2) ? (maz[pos - x*(y-2)] ? 0 : 2) : (maz[pos +x+x] ? 0 : 2)) - /* If there is a wall two columns to the right, bit 3 is 1. */ - | (pos % x >= x - 2 ? (maz[pos + 2 - x] ? 0 : 4) : (maz[pos + 2] ? 0 : 4)) - /* If there is a wall two colums to the left, bit 4 is 1. */ - | ((pos % x <= 1 ) ? (maz[pos + x - 2] ? 0 : 8) : (maz[pos-2] ? 0 : 8)))) { - - /* Note if all bits are 0, d is false, we don't do this - while loop, we don't call ourselves again, so this branch - is done. */ - - /* I see what this loop does (more or less), but I don't know - _why_ it does it this way... I also haven't figured out exactly - which values of multiple will work and which won't. */ - do { - rnd = (rnd * mvals.multiple + mvals.offset); - i = 3 & (rnd / d); - if (++c > 100) { /* Break and try to salvage something */ - i=99; /* if it looks like we're going to be */ - break; /* here forever... */ - } - } while ( !(d & ( 1 << i) ) ); - /* ...While there's *not* a wall in direction i. */ - /* (stop looping when there is) */ - - switch (i) { /* This is simple enough. */ - case 0: /* Go in the direction we just figured . . . */ - j = pos < x ? x*(y-1)+pos : pos - x; - npos = pos < (x*2) ? x*(y-2)+pos : pos - x - x; - break; - case 1: - j = pos >= x*(y-1) ? pos - x * (y-1) : pos + x; - npos = pos >= x*(y-2) ? pos - x*(y-2) : pos + x + x; - break; - case 2: - j = (pos % x) == (x - 1) ? pos + 1 - x : pos + 1; - npos = (pos % x) >= (x - 2) ? pos + 2 - x : pos + 2; - break; - case 3: - j= (pos % x) == 0 ? pos + x - 1 : pos - 1; - npos = (pos % x) <= 1 ? pos + x - 2 : pos - 2; - break; - case 99: - return 1; /* Hey neat, broken mazes! */ - break; /* (Umm... Wow... Yeah, neat.) */ - default: - break; - } - - /* And punch a hole there. */ - maz[j] = 1; - - /* Now, start again just past where we punched the hole... */ - mazegen_tileable(npos, maz, x, y, rnd); - - } /* End while(d=...) Loop */ - - return 0; -} - -static void -get_colors (GDrawable *drawable, guint8 *fg, guint8 *bg) -{ - GParam *return_vals; - gint nreturn_vals; - - switch ( gimp_drawable_type (drawable->id) ) - { - case RGBA_IMAGE: /* ASSUMPTION: Assuming the user wants entire */ - fg[3] = 255; /* area to be fully opaque. */ - bg[3] = 255; - case RGB_IMAGE: - - return_vals = gimp_run_procedure ("gimp_palette_get_foreground", - &nreturn_vals, - PARAM_END); - - if (return_vals[0].data.d_status == STATUS_SUCCESS) - { - fg[0] = return_vals[1].data.d_color.red; - fg[1] = return_vals[1].data.d_color.green; - fg[2] = return_vals[1].data.d_color.blue; - } - else - { - fg[0] = 255; - fg[1] = 255; - fg[2] = 255; - } - return_vals = gimp_run_procedure ("gimp_palette_get_background", - &nreturn_vals, - PARAM_END); - - if (return_vals[0].data.d_status == STATUS_SUCCESS) - { - bg[0] = return_vals[1].data.d_color.red; - bg[1] = return_vals[1].data.d_color.green; - bg[2] = return_vals[1].data.d_color.blue; - } - else - { - bg[0] = 0; - bg[1] = 0; - bg[2] = 0; - } - break; - case GRAYA_IMAGE: /* and again */ - fg[1] = 255; - bg[1] = 255; - case GRAY_IMAGE: - - return_vals = gimp_run_procedure ("gimp_palette_get_foreground", - &nreturn_vals, - PARAM_END); - - if (return_vals[0].data.d_status == STATUS_SUCCESS) - { - fg[0] = 0.30 * return_vals[1].data.d_color.red + - 0.59 * return_vals[1].data.d_color.green + - 0.11 * return_vals[1].data.d_color.blue; - } - else - { - fg[0] = 255; - } - - return_vals = gimp_run_procedure ("gimp_palette_get_background", - &nreturn_vals, - PARAM_END); - - if (return_vals[0].data.d_status == STATUS_SUCCESS) - { - bg[0] = 0.30 * return_vals[1].data.d_color.red + - 0.59 * return_vals[1].data.d_color.green + - 0.11 * return_vals[1].data.d_color.blue; - } - else - { - bg[0] = 0; - } - - break; - case INDEXEDA_IMAGE: - case INDEXED_IMAGE: /* FIXME: Should use current fg/bg colors. */ - g_warning("maze: get_colors: Indexed image. Using colors 15 and 0.\n"); - fg[0] = 15; /* As a plugin, I protest. *I* shouldn't be the */ - bg[0] = 0; /* one who has to deal with this colormapcrap. */ - break; - default: - break; - } -} + } /* next pr sel_rgn tile thing */ + /* #ifdef MAZE_DEBUG + maze_dump(maz,mw,mh); + #endif */ +} /* mask_maze */ +#endif /* 0 */ diff --git a/plug-ins/maze/maze.h b/plug-ins/maze/maze.h index 97f0c0002f..e501378623 100644 --- a/plug-ins/maze/maze.h +++ b/plug-ins/maze/maze.h @@ -1,5 +1,5 @@ -#define MAZE_TITLE "Maze 0.6.2" -#define MAZE_URL "http://www.poboxes.com/kevint/gimp/maze.html" +#define MAZE_TITLE "Maze 1.2.1" +#define MAZE_URL "http://www.poboxes.com/kevint/gimp/maze-help.html" #define HELP_OPENS_NEW_WINDOW FALSE @@ -8,15 +8,36 @@ latter leaves much to be desired. */ #define DIVBOX_LOOKS_LIKE_SPINBUTTON FALSE -#include "gtk/gtk.h" +/* Don't update the progress for every cell when creating a maze. + Instead, update every . . . */ +#define PRIMS_PROGRESS_UPDATE 256 + +/* Don't draw in anything that has less than + this value in the selection channel. */ +#define MAZE_ALPHA_THRESHOLD 127 + +#include "glib.h" + +typedef enum { + DEPTH_FIRST, + PRIMS_ALGORITHM +} MazeAlgoType; typedef struct { gint width; gint height; - gint seed; + guint seed; gboolean tile; gint multiple; gint offset; + MazeAlgoType algorithm; /* Interface options. */ gboolean timeseed; } MazeValues; + +enum CellTypes { + OUT, + IN, + FRONTIER, + MASKED +}; diff --git a/plug-ins/maze/maze_face.c b/plug-ins/maze/maze_face.c index eb76db3734..67173997f3 100644 --- a/plug-ins/maze/maze_face.c +++ b/plug-ins/maze/maze_face.c @@ -1,4 +1,4 @@ -/* maze_face.c, version 0.6.2, March 7, 1998. +/* maze_face.c * User interface for plug-in-maze. * * Implemented as a GIMP 0.99 Plugin by @@ -19,7 +19,7 @@ * * 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. + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ @@ -29,7 +29,6 @@ #endif /* MAZE_DEBUG */ #include "maze.h" - #include "libgimp/gimp.h" #include "libgimp/gimpui.h" @@ -113,6 +112,8 @@ static void div_button_callback (GtkWidget *button, GtkWidget *entry); static void div_entry_callback (GtkWidget *entry, GtkWidget *friend); static void height_width_callback (gint width, GtkWidget **div_entry); static void toggle_callback (GtkWidget *widget, gboolean *data); +static void alg_radio_callback (GtkWidget *widget, gpointer data); + static GtkWidget* divbox_new (guint *max, GtkWidget *friend, GtkWidget **div_entry); @@ -125,7 +126,7 @@ static void div_buttonr_callback (GtkObject *object); /* entscale stuff begin */ static GtkWidget* entscale_int_new ( GtkWidget *table, gint x, gint y, gchar *caption, gint *intvar, - gint min, gint max, gint constraint, + gint min, gint max, gboolean constraint, EntscaleIntCallbackFunc callback, gpointer data ); @@ -168,6 +169,8 @@ gint maze_dialog() GtkWidget *div_x_hbox, *div_y_hbox; GtkWidget *div_x_label, *div_y_label, *div_x_entry, *div_y_entry; + GtkWidget *alg_box, *alg_button; + gchar **argv; gint argc; gchar buffer[32]; @@ -239,7 +242,6 @@ gint maze_dialog() GTK_FRAME(msg_frame)->label) + 7); #endif - /* Set up Options page */ frame = gtk_frame_new ("Maze Options"); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN); @@ -321,8 +323,6 @@ gint maze_dialog() gtk_widget_show (div_y_hbox); - - /* Add Options page to notebook */ gtk_widget_show (frame); gtk_widget_show (table); @@ -331,11 +331,10 @@ gint maze_dialog() gtk_label_new ("Options")); /* Set up other page */ - frame = gtk_frame_new ("Don't change these"); + frame = gtk_frame_new ("At Your Own Risk"); 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); + table = gtk_table_new (4, 2, FALSE); gtk_container_border_width (GTK_CONTAINER (table), 10); gtk_container_add (GTK_CONTAINER (frame), table); @@ -399,6 +398,35 @@ gint maze_dialog() gtk_widget_show (time_button); gtk_widget_show (seed_hbox); + label = gtk_label_new("Algorithm"); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), label, 0, 1, 3, 4, + GTK_FILL, 0, 5, 5); + gtk_widget_show (label); + + alg_box=gtk_vbox_new(FALSE, 5); + gtk_table_attach (GTK_TABLE (table), alg_box, 1, 2, 3, 4, + GTK_FILL, 0, 5, 5); + gtk_widget_show (alg_box); + + alg_button=gtk_radio_button_new_with_label (NULL,"Depth First"); + gtk_signal_connect(GTK_OBJECT(alg_button),"toggled", + GTK_SIGNAL_FUNC(alg_radio_callback), (gpointer)DEPTH_FIRST); + if(mvals.algorithm==DEPTH_FIRST) + gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(alg_button), TRUE); + gtk_container_add(GTK_CONTAINER(alg_box),alg_button); + gtk_widget_show(alg_button); + + alg_button=gtk_radio_button_new_with_label (gtk_radio_button_group( + GTK_RADIO_BUTTON(alg_button)), "Prim's Algorithm"); + gtk_signal_connect(GTK_OBJECT(alg_button),"toggled", + GTK_SIGNAL_FUNC(alg_radio_callback), (gpointer)PRIMS_ALGORITHM); + if(mvals.algorithm==PRIMS_ALGORITHM) + gtk_toggle_button_set_state(GTK_TOGGLE_BUTTON(alg_button), TRUE); + + gtk_container_add(GTK_CONTAINER(alg_box),alg_button); + gtk_widget_show(alg_button); + /* Add Advanced page to notebook */ gtk_widget_show (frame); gtk_widget_show (table); @@ -633,21 +661,25 @@ maze_close_callback (GtkWidget *widget, static void maze_help (GtkWidget *widget, gpointer foo) { - void *bar=(void *)NULL; + char *proc_blurb, *proc_help, *proc_author, *proc_copyright, *proc_date; + int proc_type, nparams, nreturn_vals; + GParamDef *params, *return_vals; gint baz; - if (gimp_query_procedure("extension_web_browser", - bar, bar, bar, bar, bar, - bar, bar, bar, bar, bar)) { - maze_msg("Opening " MAZE_URL); - gimp_run_procedure("extension_web_browser", &baz, - PARAM_INT32, RUN_NONINTERACTIVE, - PARAM_STRING, MAZE_URL, - PARAM_INT32, HELP_OPENS_NEW_WINDOW, - PARAM_END); + if (gimp_query_procedure("extension_web_browser", + &proc_blurb, &proc_help, + &proc_author, &proc_copyright, &proc_date, + &proc_type, &nparams, &nreturn_vals, + ¶ms, &return_vals)) { + maze_msg("Opening " MAZE_URL); + gimp_run_procedure("extension_web_browser", &baz, + PARAM_INT32, RUN_NONINTERACTIVE, + PARAM_STRING, MAZE_URL, + PARAM_INT32, HELP_OPENS_NEW_WINDOW, + PARAM_END); } else { - maze_msg("See " MAZE_URL); - } + maze_msg("See " MAZE_URL); + } } static void @@ -681,6 +713,12 @@ toggle_callback (GtkWidget *widget, gboolean *data) *data = GTK_TOGGLE_BUTTON (widget)->active; } +static void +alg_radio_callback (GtkWidget *widget, gpointer data) +{ + mvals.algorithm=(MazeAlgoType)data; +} + /* ==================================================================== */ /* As found in pixelize.c, * hacked to return a pointer to the entry widget. */ @@ -710,7 +748,7 @@ toggle_callback (GtkWidget *widget, gboolean *data) static GtkWidget* entscale_int_new ( GtkWidget *table, gint x, gint y, gchar *caption, gint *intvar, - gint min, gint max, gint constraint, + gint min, gint max, gboolean constraint, EntscaleIntCallbackFunc callback, gpointer call_data) { diff --git a/plug-ins/script-fu/scripts/circuit.scm b/plug-ins/script-fu/scripts/circuit.scm index 6bf14f4fec..ce8be48077 100644 --- a/plug-ins/script-fu/scripts/circuit.scm +++ b/plug-ins/script-fu/scripts/circuit.scm @@ -92,7 +92,7 @@ (gimp-palette-set-foreground '(14 14 14)))) (gimp-selection-load image active-selection) - (plug-in-maze 1 image active-layer 5 5 TRUE seed 57 1) + (plug-in-maze 1 image active-layer 5 5 TRUE 0 seed 57 1) (plug-in-oilify 1 image active-layer mask-size 0) (plug-in-edge 1 image active-layer 2 1) (gimp-desaturate image active-layer)