summaryrefslogtreecommitdiffstats
path: root/plug-ins/common/blinds.c
diff options
context:
space:
mode:
Diffstat (limited to 'plug-ins/common/blinds.c')
-rw-r--r--plug-ins/common/blinds.c730
1 files changed, 730 insertions, 0 deletions
diff --git a/plug-ins/common/blinds.c b/plug-ins/common/blinds.c
new file mode 100644
index 0000000..a0ceeea
--- /dev/null
+++ b/plug-ins/common/blinds.c
@@ -0,0 +1,730 @@
+/*
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This is a plug-in for GIMP.
+ *
+ * Blinds plug-in. Distort an image as though it was stuck to
+ * window blinds and the blinds where opened/closed.
+ *
+ * Copyright (C) 1997 Andy Thomas alt@picnic.demon.co.uk
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ * A fair proprotion of this code was taken from the Whirl plug-in
+ * which was copyrighted by Federico Mena Quintero (as below).
+ *
+ * Whirl plug-in --- distort an image into a whirlpool
+ * Copyright (C) 1997 Federico Mena Quintero
+ *
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#include "libgimp/stdplugins-intl.h"
+
+/***** Magic numbers *****/
+
+#define PLUG_IN_PROC "plug-in-blinds"
+#define PLUG_IN_BINARY "blinds"
+#define PLUG_IN_ROLE "gimp-blinds"
+
+#define SCALE_WIDTH 150
+
+#define MAX_FANS 100
+
+/* Variables set in dialog box */
+typedef struct data
+{
+ gint angledsp;
+ gint numsegs;
+ GimpOrientationType orientation;
+ gboolean bg_trans;
+} BlindVals;
+
+
+static void query (void);
+static void run (const gchar *name,
+ gint nparams,
+ const GimpParam *param,
+ gint *nreturn_vals,
+ GimpParam **return_vals);
+
+static gboolean blinds_dialog (gint32 drawable_id);
+
+static void dialog_update_preview (gpointer drawable_id,
+ GimpPreview *preview);
+static void apply_blinds (gint32 drawable_id);
+
+
+/* Array to hold each size of fans. And no there are not each the
+ * same size (rounding errors...)
+ */
+
+static gint fanwidths[MAX_FANS];
+
+/* Values when first invoked */
+static BlindVals bvals =
+{
+ 30,
+ 3,
+ GIMP_ORIENTATION_HORIZONTAL,
+ FALSE
+};
+
+const GimpPlugInInfo PLUG_IN_INFO =
+{
+ NULL, /* init_proc */
+ NULL, /* quit_proc */
+ query, /* query_proc */
+ run, /* run_proc */
+};
+
+
+MAIN ()
+
+
+static void
+query (void)
+{
+ static const GimpParamDef args[] =
+ {
+ { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
+ { GIMP_PDB_IMAGE, "image", "Input image (unused)" },
+ { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" },
+ { GIMP_PDB_INT32, "angle-dsp", "Angle of Displacement" },
+ { GIMP_PDB_INT32, "num-segments", "Number of segments in blinds" },
+ { GIMP_PDB_INT32, "orientation", "The orientation { ORIENTATION-HORIZONTAL (0), ORIENTATION-VERTICAL (1) }" },
+ { GIMP_PDB_INT32, "bg-transparent", "Background transparent { FALSE, TRUE }" }
+ };
+
+ gimp_install_procedure (PLUG_IN_PROC,
+ N_("Simulate an image painted on window blinds"),
+ "More here later",
+ "Andy Thomas",
+ "Andy Thomas",
+ "1997",
+ N_("_Blinds..."),
+ "RGB*, GRAY*",
+ GIMP_PLUGIN,
+ G_N_ELEMENTS (args), 0,
+ args, NULL);
+}
+
+static void
+run (const gchar *name,
+ gint nparams,
+ const GimpParam *param,
+ gint *nreturn_vals,
+ GimpParam **return_vals)
+{
+ static GimpParam values[1];
+ gint32 drawable_id;
+ GimpRunMode run_mode;
+ GimpPDBStatusType status = GIMP_PDB_SUCCESS;
+
+ INIT_I18N ();
+ gegl_init (NULL, NULL);
+
+ *nreturn_vals = 1;
+ *return_vals = values;
+
+ values[0].type = GIMP_PDB_STATUS;
+ values[0].data.d_status = status;
+
+ run_mode = param[0].data.d_int32;
+ drawable_id = param[2].data.d_drawable;
+
+ switch (run_mode)
+ {
+ case GIMP_RUN_INTERACTIVE:
+ gimp_get_data (PLUG_IN_PROC, &bvals);
+ if (! blinds_dialog (drawable_id))
+ return;
+ break;
+
+ case GIMP_RUN_NONINTERACTIVE:
+ if (nparams != 7)
+ status = GIMP_PDB_CALLING_ERROR;
+
+ if (status == GIMP_PDB_SUCCESS)
+ {
+ bvals.angledsp = param[3].data.d_int32;
+ bvals.numsegs = param[4].data.d_int32;
+ bvals.orientation = param[5].data.d_int32;
+ bvals.bg_trans = param[6].data.d_int32;
+ }
+ break;
+
+ case GIMP_RUN_WITH_LAST_VALS:
+ gimp_get_data (PLUG_IN_PROC, &bvals);
+ break;
+
+ default:
+ break;
+ }
+
+ if (gimp_drawable_is_rgb (drawable_id) ||
+ gimp_drawable_is_gray (drawable_id))
+ {
+ gimp_progress_init (_("Adding blinds"));
+
+ apply_blinds (drawable_id);
+
+ if (run_mode != GIMP_RUN_NONINTERACTIVE)
+ gimp_displays_flush ();
+
+ if (run_mode == GIMP_RUN_INTERACTIVE)
+ gimp_set_data (PLUG_IN_PROC, &bvals, sizeof (BlindVals));
+ }
+ else
+ {
+ status = GIMP_PDB_EXECUTION_ERROR;
+ }
+
+ values[0].data.d_status = status;
+}
+
+
+static gboolean
+blinds_dialog (gint32 drawable_id)
+{
+ GtkWidget *dialog;
+ GtkWidget *main_vbox;
+ GtkWidget *preview;
+ GtkWidget *hbox;
+ GtkWidget *frame;
+ GtkWidget *table;
+ GtkObject *size_data;
+ GtkWidget *toggle;
+ GtkWidget *horizontal;
+ GtkWidget *vertical;
+ gboolean run;
+
+ gimp_ui_init (PLUG_IN_BINARY, TRUE);
+
+ dialog = gimp_dialog_new (_("Blinds"), PLUG_IN_ROLE,
+ NULL, 0,
+ gimp_standard_help_func, PLUG_IN_PROC,
+
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ _("_OK"), GTK_RESPONSE_OK,
+
+ NULL);
+
+ gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
+ GTK_RESPONSE_OK,
+ GTK_RESPONSE_CANCEL,
+ -1);
+
+ gimp_window_set_transient (GTK_WINDOW (dialog));
+
+ main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
+ gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12);
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
+ main_vbox, TRUE, TRUE, 0);
+ gtk_widget_show (main_vbox);
+
+ preview = gimp_aspect_preview_new_from_drawable_id (drawable_id);
+ gtk_box_pack_start (GTK_BOX (main_vbox), preview, TRUE, TRUE, 0);
+ gtk_widget_show (preview);
+
+ g_signal_connect_swapped (preview, "invalidated",
+ G_CALLBACK (dialog_update_preview),
+ GINT_TO_POINTER (drawable_id));
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
+ gtk_box_pack_start (GTK_BOX (main_vbox), hbox, FALSE, FALSE, 0);
+ gtk_widget_show (hbox);
+
+ frame =
+ gimp_int_radio_group_new (TRUE, _("Orientation"),
+ G_CALLBACK (gimp_radio_button_update),
+ &bvals.orientation, bvals.orientation,
+
+ _("_Horizontal"), GIMP_ORIENTATION_HORIZONTAL,
+ &horizontal,
+
+ _("_Vertical"), GIMP_ORIENTATION_VERTICAL,
+ &vertical,
+
+ NULL);
+ gtk_box_pack_start (GTK_BOX (hbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ g_signal_connect_swapped (horizontal, "toggled",
+ G_CALLBACK (gimp_preview_invalidate),
+ preview);
+ g_signal_connect_swapped (vertical, "toggled",
+ G_CALLBACK (gimp_preview_invalidate),
+ preview);
+
+ frame = gimp_frame_new (_("Background"));
+ gtk_box_pack_start (GTK_BOX (hbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ toggle = gtk_check_button_new_with_mnemonic (_("_Transparent"));
+ gtk_container_add (GTK_CONTAINER (frame), toggle);
+ gtk_widget_show (toggle);
+
+ g_signal_connect (toggle, "toggled",
+ G_CALLBACK (gimp_toggle_button_update),
+ &bvals.bg_trans);
+ g_signal_connect_swapped (toggle, "toggled",
+ G_CALLBACK (gimp_preview_invalidate),
+ preview);
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), bvals.bg_trans);
+
+ if (! gimp_drawable_has_alpha (drawable_id))
+ {
+ gtk_widget_set_sensitive (toggle, FALSE);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), FALSE);
+ }
+
+ table = gtk_table_new (2, 3, FALSE);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 6);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 2);
+ gtk_box_pack_start (GTK_BOX (main_vbox), table, FALSE, FALSE, 0);
+ gtk_widget_show (table);
+
+ size_data = gimp_scale_entry_new (GTK_TABLE (table), 0, 0,
+ _("_Displacement:"), SCALE_WIDTH, 0,
+ bvals.angledsp, 1, 90, 1, 15, 0,
+ TRUE, 0, 0,
+ NULL, NULL);
+ g_signal_connect (size_data, "value-changed",
+ G_CALLBACK (gimp_int_adjustment_update),
+ &bvals.angledsp);
+ g_signal_connect_swapped (size_data, "value-changed",
+ G_CALLBACK (gimp_preview_invalidate),
+ preview);
+
+ size_data = gimp_scale_entry_new (GTK_TABLE (table), 0, 1,
+ _("_Number of segments:"), SCALE_WIDTH, 0,
+ bvals.numsegs, 1, MAX_FANS, 1, 2, 0,
+ TRUE, 0, 0,
+ NULL, NULL);
+ g_signal_connect (size_data, "value-changed",
+ G_CALLBACK (gimp_int_adjustment_update),
+ &bvals.numsegs);
+ g_signal_connect_swapped (size_data, "value-changed",
+ G_CALLBACK (gimp_preview_invalidate),
+ preview);
+
+ gtk_widget_show (dialog);
+
+ run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
+
+ gtk_widget_destroy (dialog);
+
+ return run;
+}
+
+static void
+blindsapply (guchar *srow,
+ guchar *drow,
+ gint width,
+ gint bpp,
+ guchar *bg)
+{
+ guchar *src;
+ guchar *dst;
+ gint i,j,k;
+ gdouble ang;
+ gint available;
+
+ /* Make the row 'shrink' around points along its length */
+ /* The bvals.numsegs determines how many segments to slip it in to */
+ /* The angle is the conceptual 'rotation' of each of these segments */
+
+ /* Note the row is considered to be made up of a two dim array actual
+ * pixel locations and the RGB color at these locations.
+ */
+
+ /* In the process copy the src row to the destination row */
+
+ /* Fill in with background color ? */
+ for (i = 0 ; i < width ; i++)
+ {
+ dst = &drow[i*bpp];
+
+ for (j = 0 ; j < bpp; j++)
+ {
+ dst[j] = bg[j];
+ }
+ }
+
+ /* Apply it */
+
+ available = width;
+ for (i = 0; i < bvals.numsegs; i++)
+ {
+ /* Width of segs are variable */
+ fanwidths[i] = available / (bvals.numsegs - i);
+ available -= fanwidths[i];
+ }
+
+ /* do center points first - just for fun...*/
+ available = 0;
+ for (k = 1; k <= bvals.numsegs; k++)
+ {
+ int point;
+
+ point = available + fanwidths[k - 1] / 2;
+
+ available += fanwidths[k - 1];
+
+ src = &srow[point * bpp];
+ dst = &drow[point * bpp];
+
+ /* Copy pixels across */
+ for (j = 0 ; j < bpp; j++)
+ {
+ dst[j] = src[j];
+ }
+ }
+
+ /* Disp for each point */
+ ang = (bvals.angledsp * 2 * G_PI) / 360; /* Angle in rads */
+ ang = (1 - fabs (cos (ang)));
+
+ available = 0;
+ for (k = 0 ; k < bvals.numsegs; k++)
+ {
+ int dx; /* Amount to move by */
+ int fw;
+
+ for (i = 0 ; i < (fanwidths[k]/2) ; i++)
+ {
+ /* Copy pixels across of left half of fan */
+ fw = fanwidths[k] / 2;
+ dx = (int) (ang * ((double) (fw - (double)(i % fw))));
+
+ src = &srow[(available + i) * bpp];
+ dst = &drow[(available + i + dx) * bpp];
+
+ for (j = 0; j < bpp; j++)
+ {
+ dst[j] = src[j];
+ }
+
+ /* Right side */
+ j = i + 1;
+ src = &srow[(available + fanwidths[k] - j
+ - (fanwidths[k] % 2)) * bpp];
+ dst = &drow[(available + fanwidths[k] - j
+ - (fanwidths[k] % 2) - dx) * bpp];
+
+ for (j = 0; j < bpp; j++)
+ {
+ dst[j] = src[j];
+ }
+ }
+
+ available += fanwidths[k];
+ }
+}
+
+static void
+dialog_update_preview (gpointer drawable_ptr,
+ GimpPreview *preview)
+{
+ gint32 drawable_id = GPOINTER_TO_INT (drawable_ptr);
+ gint y;
+ guchar *p, *buffer, *cache;
+ GimpRGB background;
+ guchar bg[4];
+ gint width, height, bpp;
+
+ gimp_preview_get_size (preview, &width, &height);
+ cache = gimp_drawable_get_thumbnail_data (drawable_id,
+ &width, &height, &bpp);
+ p = cache;
+
+ gimp_context_get_background (&background);
+
+ if (bvals.bg_trans)
+ gimp_rgb_set_alpha (&background, 0.0);
+
+ if (gimp_drawable_is_gray (drawable_id))
+ {
+ bg[0] = gimp_rgb_luminance_uchar (&background);
+ gimp_rgba_get_uchar (&background, NULL, NULL, NULL, bg + 3);
+ }
+ else
+ {
+ gimp_rgba_get_uchar (&background, bg, bg + 1, bg + 2, bg + 3);
+ }
+
+ buffer = g_new (guchar, width * height * bpp);
+
+ if (bvals.orientation == GIMP_ORIENTATION_VERTICAL)
+ {
+ for (y = 0; y < height; y++)
+ {
+ blindsapply (p,
+ buffer + y * width * bpp,
+ width,
+ bpp, bg);
+ p += width * bpp;
+ }
+ }
+ else
+ {
+ /* Horizontal blinds */
+ /* Apply the blinds algo to a single column -
+ * this act as a transfomation matrix for the
+ * rows. Make row 0 invalid so we can find it again!
+ */
+ gint i;
+ guchar *sr = g_new (guchar, height * 4);
+ guchar *dr = g_new0 (guchar, height * 4);
+ guchar dummybg[4] = {0, 0, 0, 0};
+
+ /* Fill in with background color ? */
+ for (i = 0 ; i < width ; i++)
+ {
+ gint j;
+ gint bd = bpp;
+ guchar *dst;
+ dst = &buffer[i * bd];
+
+ for (j = 0 ; j < bd; j++)
+ {
+ dst[j] = bg[j];
+ }
+ }
+
+ for ( y = 0 ; y < height; y++)
+ {
+ sr[y] = y+1;
+ }
+
+ /* Bit of a fiddle since blindsapply really works on an image
+ * row not a set of bytes. - preview can't be > 255
+ * or must make dr sr int rows.
+ */
+ blindsapply (sr, dr, height, 1, dummybg);
+
+ for (y = 0; y < height; y++)
+ {
+ if (dr[y] == 0)
+ {
+ /* Draw background line */
+ p = buffer;
+ }
+ else
+ {
+ /* Draw line from src */
+ p = cache +
+ (width * bpp * (dr[y] - 1));
+ }
+ memcpy (buffer + y * width * bpp,
+ p,
+ width * bpp);
+ }
+ g_free (sr);
+ g_free (dr);
+ }
+
+ gimp_preview_draw_buffer (preview, buffer, width * bpp);
+
+ g_free (buffer);
+ g_free (cache);
+}
+
+/* STEP tells us how many rows/columns to gulp down in one go... */
+/* Note all the "4" literals around here are to do with the depths
+ * of the images. Makes it easier to deal with for my small brain.
+ */
+
+#define STEP 40
+
+static void
+apply_blinds (gint32 drawable_id)
+{
+ GeglBuffer *src_buffer;
+ GeglBuffer *dest_buffer;
+ const Babl *format;
+ guchar *src_rows, *des_rows;
+ gint bytes;
+ gint x, y;
+ GimpRGB background;
+ guchar bg[4];
+ gint sel_x1, sel_y1;
+ gint sel_width, sel_height;
+
+ gimp_context_get_background (&background);
+
+ if (bvals.bg_trans)
+ gimp_rgb_set_alpha (&background, 0.0);
+
+ gimp_rgba_get_uchar (&background, bg, bg + 1, bg + 2, bg + 3);
+
+ if (! gimp_drawable_mask_intersect (drawable_id,
+ &sel_x1, &sel_y1,
+ &sel_width, &sel_height))
+ return;
+
+ if (gimp_drawable_has_alpha (drawable_id))
+ format = babl_format ("R'G'B'A u8");
+ else
+ format = babl_format ("R'G'B' u8");
+
+ bytes = babl_format_get_bytes_per_pixel (format);
+
+ src_buffer = gimp_drawable_get_buffer (drawable_id);
+ dest_buffer = gimp_drawable_get_shadow_buffer (drawable_id);
+
+ src_rows = g_new (guchar, MAX (sel_width, sel_height) * bytes * STEP);
+ des_rows = g_new (guchar, MAX (sel_width, sel_height) * bytes * STEP);
+
+ if (bvals.orientation == GIMP_ORIENTATION_VERTICAL)
+ {
+ for (y = 0; y < sel_height; y += STEP)
+ {
+ gint rr;
+ gint step;
+
+ if ((y + STEP) > sel_height)
+ step = sel_height - y;
+ else
+ step = STEP;
+
+ gegl_buffer_get (src_buffer,
+ GEGL_RECTANGLE (sel_x1, sel_y1 + y,
+ sel_width, step), 1.0,
+ format, src_rows,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+
+ /* OK I could make this better */
+ for (rr = 0; rr < STEP; rr++)
+ blindsapply (src_rows + (sel_width * rr * bytes),
+ des_rows + (sel_width * rr * bytes),
+ sel_width, bytes, bg);
+
+ gegl_buffer_set (dest_buffer,
+ GEGL_RECTANGLE (sel_x1, sel_y1 + y,
+ sel_width, step), 0,
+ format, des_rows,
+ GEGL_AUTO_ROWSTRIDE);
+
+ gimp_progress_update ((double) y / (double) sel_height);
+ }
+ }
+ else
+ {
+ /* Horizontal blinds */
+ /* Apply the blinds algo to a single column -
+ * this act as a transfomation matrix for the
+ * rows. Make row 0 invalid so we can find it again!
+ */
+ gint i;
+ gint *sr = g_new (gint, sel_height * bytes);
+ gint *dr = g_new (gint, sel_height * bytes);
+ guchar *dst = g_new (guchar, STEP * bytes);
+ guchar dummybg[4];
+
+ memset (dummybg, 0, 4);
+ memset (dr, 0, sel_height * bytes); /* all dr rows are background rows */
+ for (y = 0; y < sel_height; y++)
+ {
+ sr[y] = y+1;
+ }
+
+ /* Hmmm. does this work portably? */
+ /* This "swaps" the integers around that are held in in the
+ * sr & dr arrays.
+ */
+ blindsapply ((guchar *) sr, (guchar *) dr,
+ sel_height, sizeof (gint), dummybg);
+
+ /* Fill in with background color ? */
+ for (i = 0 ; i < STEP ; i++)
+ {
+ int j;
+ guchar *bgdst;
+ bgdst = &dst[i * bytes];
+
+ for (j = 0 ; j < bytes; j++)
+ {
+ bgdst[j] = bg[j];
+ }
+ }
+
+ for (x = 0; x < sel_width; x += STEP)
+ {
+ int rr;
+ int step;
+ guchar *p;
+
+ if((x + STEP) > sel_width)
+ step = sel_width - x;
+ else
+ step = STEP;
+
+ gegl_buffer_get (src_buffer,
+ GEGL_RECTANGLE (sel_x1 + x, sel_y1,
+ step, sel_height), 1.0,
+ format, src_rows,
+ GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+
+ /* OK I could make this better */
+ for (rr = 0; rr < sel_height; rr++)
+ {
+ if(dr[rr] == 0)
+ {
+ /* Draw background line */
+ p = dst;
+ }
+ else
+ {
+ /* Draw line from src */
+ p = src_rows + (step * bytes * (dr[rr] - 1));
+ }
+ memcpy (des_rows + (rr * step * bytes), p,
+ step * bytes);
+ }
+
+ gegl_buffer_set (dest_buffer,
+ GEGL_RECTANGLE (sel_x1 + x, sel_y1,
+ step, sel_height), 0,
+ format, des_rows,
+ GEGL_AUTO_ROWSTRIDE);
+
+ gimp_progress_update ((double) x / (double) sel_width);
+ }
+
+ g_free (dst);
+ g_free (sr);
+ g_free (dr);
+ }
+
+ g_free (src_rows);
+ g_free (des_rows);
+
+ g_object_unref (src_buffer);
+ g_object_unref (dest_buffer);
+
+ gimp_progress_update (1.0);
+
+ gimp_drawable_merge_shadow (drawable_id, TRUE);
+ gimp_drawable_update (drawable_id,
+ sel_x1, sel_y1, sel_width, sel_height);
+}