diff options
Diffstat (limited to 'plug-ins/common/blinds.c')
-rw-r--r-- | plug-ins/common/blinds.c | 730 |
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); +} |