1 /*
2 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3 *
4 * This is a plug-in for GIMP.
5 *
6 * Blinds plug-in. Distort an image as though it was stuck to
7 * window blinds and the blinds where opened/closed.
8 *
9 * Copyright (C) 1997 Andy Thomas alt@picnic.demon.co.uk
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see <https://www.gnu.org/licenses/>.
23 *
24 * A fair proprotion of this code was taken from the Whirl plug-in
25 * which was copyrighted by Federico Mena Quintero (as below).
26 *
27 * Whirl plug-in --- distort an image into a whirlpool
28 * Copyright (C) 1997 Federico Mena Quintero
29 *
30 */
31
32 #include "config.h"
33
34 #include <string.h>
35
36 #include <libgimp/gimp.h>
37 #include <libgimp/gimpui.h>
38
39 #include "libgimp/stdplugins-intl.h"
40
41 /***** Magic numbers *****/
42
43 #define PLUG_IN_PROC "plug-in-blinds"
44 #define PLUG_IN_BINARY "blinds"
45 #define PLUG_IN_ROLE "gimp-blinds"
46
47 #define SCALE_WIDTH 150
48
49 #define MAX_FANS 100
50
51 /* Variables set in dialog box */
52 typedef struct data
53 {
54 gint angledsp;
55 gint numsegs;
56 GimpOrientationType orientation;
57 gboolean bg_trans;
58 } BlindVals;
59
60
61 static void query (void);
62 static void run (const gchar *name,
63 gint nparams,
64 const GimpParam *param,
65 gint *nreturn_vals,
66 GimpParam **return_vals);
67
68 static gboolean blinds_dialog (gint32 drawable_id);
69
70 static void dialog_update_preview (gpointer drawable_id,
71 GimpPreview *preview);
72 static void apply_blinds (gint32 drawable_id);
73
74
75 /* Array to hold each size of fans. And no there are not each the
76 * same size (rounding errors...)
77 */
78
79 static gint fanwidths[MAX_FANS];
80
81 /* Values when first invoked */
82 static BlindVals bvals =
83 {
84 30,
85 3,
86 GIMP_ORIENTATION_HORIZONTAL,
87 FALSE
88 };
89
90 const GimpPlugInInfo PLUG_IN_INFO =
91 {
92 NULL, /* init_proc */
93 NULL, /* quit_proc */
94 query, /* query_proc */
95 run, /* run_proc */
96 };
97
98
MAIN()99 MAIN ()
100
101
102 static void
103 query (void)
104 {
105 static const GimpParamDef args[] =
106 {
107 { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
108 { GIMP_PDB_IMAGE, "image", "Input image (unused)" },
109 { GIMP_PDB_DRAWABLE, "drawable", "Input drawable" },
110 { GIMP_PDB_INT32, "angle-dsp", "Angle of Displacement" },
111 { GIMP_PDB_INT32, "num-segments", "Number of segments in blinds" },
112 { GIMP_PDB_INT32, "orientation", "The orientation { ORIENTATION-HORIZONTAL (0), ORIENTATION-VERTICAL (1) }" },
113 { GIMP_PDB_INT32, "bg-transparent", "Background transparent { FALSE, TRUE }" }
114 };
115
116 gimp_install_procedure (PLUG_IN_PROC,
117 N_("Simulate an image painted on window blinds"),
118 "More here later",
119 "Andy Thomas",
120 "Andy Thomas",
121 "1997",
122 N_("_Blinds..."),
123 "RGB*, GRAY*",
124 GIMP_PLUGIN,
125 G_N_ELEMENTS (args), 0,
126 args, NULL);
127 }
128
129 static void
run(const gchar * name,gint nparams,const GimpParam * param,gint * nreturn_vals,GimpParam ** return_vals)130 run (const gchar *name,
131 gint nparams,
132 const GimpParam *param,
133 gint *nreturn_vals,
134 GimpParam **return_vals)
135 {
136 static GimpParam values[1];
137 gint32 drawable_id;
138 GimpRunMode run_mode;
139 GimpPDBStatusType status = GIMP_PDB_SUCCESS;
140
141 INIT_I18N ();
142 gegl_init (NULL, NULL);
143
144 *nreturn_vals = 1;
145 *return_vals = values;
146
147 values[0].type = GIMP_PDB_STATUS;
148 values[0].data.d_status = status;
149
150 run_mode = param[0].data.d_int32;
151 drawable_id = param[2].data.d_drawable;
152
153 switch (run_mode)
154 {
155 case GIMP_RUN_INTERACTIVE:
156 gimp_get_data (PLUG_IN_PROC, &bvals);
157 if (! blinds_dialog (drawable_id))
158 return;
159 break;
160
161 case GIMP_RUN_NONINTERACTIVE:
162 if (nparams != 7)
163 status = GIMP_PDB_CALLING_ERROR;
164
165 if (status == GIMP_PDB_SUCCESS)
166 {
167 bvals.angledsp = param[3].data.d_int32;
168 bvals.numsegs = param[4].data.d_int32;
169 bvals.orientation = param[5].data.d_int32;
170 bvals.bg_trans = param[6].data.d_int32;
171 }
172 break;
173
174 case GIMP_RUN_WITH_LAST_VALS:
175 gimp_get_data (PLUG_IN_PROC, &bvals);
176 break;
177
178 default:
179 break;
180 }
181
182 if (gimp_drawable_is_rgb (drawable_id) ||
183 gimp_drawable_is_gray (drawable_id))
184 {
185 gimp_progress_init (_("Adding blinds"));
186
187 apply_blinds (drawable_id);
188
189 if (run_mode != GIMP_RUN_NONINTERACTIVE)
190 gimp_displays_flush ();
191
192 if (run_mode == GIMP_RUN_INTERACTIVE)
193 gimp_set_data (PLUG_IN_PROC, &bvals, sizeof (BlindVals));
194 }
195 else
196 {
197 status = GIMP_PDB_EXECUTION_ERROR;
198 }
199
200 values[0].data.d_status = status;
201 }
202
203
204 static gboolean
blinds_dialog(gint32 drawable_id)205 blinds_dialog (gint32 drawable_id)
206 {
207 GtkWidget *dialog;
208 GtkWidget *main_vbox;
209 GtkWidget *preview;
210 GtkWidget *hbox;
211 GtkWidget *frame;
212 GtkWidget *table;
213 GtkObject *size_data;
214 GtkWidget *toggle;
215 GtkWidget *horizontal;
216 GtkWidget *vertical;
217 gboolean run;
218
219 gimp_ui_init (PLUG_IN_BINARY, TRUE);
220
221 dialog = gimp_dialog_new (_("Blinds"), PLUG_IN_ROLE,
222 NULL, 0,
223 gimp_standard_help_func, PLUG_IN_PROC,
224
225 _("_Cancel"), GTK_RESPONSE_CANCEL,
226 _("_OK"), GTK_RESPONSE_OK,
227
228 NULL);
229
230 gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
231 GTK_RESPONSE_OK,
232 GTK_RESPONSE_CANCEL,
233 -1);
234
235 gimp_window_set_transient (GTK_WINDOW (dialog));
236
237 main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
238 gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12);
239 gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
240 main_vbox, TRUE, TRUE, 0);
241 gtk_widget_show (main_vbox);
242
243 preview = gimp_aspect_preview_new_from_drawable_id (drawable_id);
244 gtk_box_pack_start (GTK_BOX (main_vbox), preview, TRUE, TRUE, 0);
245 gtk_widget_show (preview);
246
247 g_signal_connect_swapped (preview, "invalidated",
248 G_CALLBACK (dialog_update_preview),
249 GINT_TO_POINTER (drawable_id));
250
251 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
252 gtk_box_pack_start (GTK_BOX (main_vbox), hbox, FALSE, FALSE, 0);
253 gtk_widget_show (hbox);
254
255 frame =
256 gimp_int_radio_group_new (TRUE, _("Orientation"),
257 G_CALLBACK (gimp_radio_button_update),
258 &bvals.orientation, bvals.orientation,
259
260 _("_Horizontal"), GIMP_ORIENTATION_HORIZONTAL,
261 &horizontal,
262
263 _("_Vertical"), GIMP_ORIENTATION_VERTICAL,
264 &vertical,
265
266 NULL);
267 gtk_box_pack_start (GTK_BOX (hbox), frame, FALSE, FALSE, 0);
268 gtk_widget_show (frame);
269
270 g_signal_connect_swapped (horizontal, "toggled",
271 G_CALLBACK (gimp_preview_invalidate),
272 preview);
273 g_signal_connect_swapped (vertical, "toggled",
274 G_CALLBACK (gimp_preview_invalidate),
275 preview);
276
277 frame = gimp_frame_new (_("Background"));
278 gtk_box_pack_start (GTK_BOX (hbox), frame, FALSE, FALSE, 0);
279 gtk_widget_show (frame);
280
281 toggle = gtk_check_button_new_with_mnemonic (_("_Transparent"));
282 gtk_container_add (GTK_CONTAINER (frame), toggle);
283 gtk_widget_show (toggle);
284
285 g_signal_connect (toggle, "toggled",
286 G_CALLBACK (gimp_toggle_button_update),
287 &bvals.bg_trans);
288 g_signal_connect_swapped (toggle, "toggled",
289 G_CALLBACK (gimp_preview_invalidate),
290 preview);
291
292 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), bvals.bg_trans);
293
294 if (! gimp_drawable_has_alpha (drawable_id))
295 {
296 gtk_widget_set_sensitive (toggle, FALSE);
297 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), FALSE);
298 }
299
300 table = gtk_table_new (2, 3, FALSE);
301 gtk_table_set_col_spacings (GTK_TABLE (table), 6);
302 gtk_table_set_row_spacings (GTK_TABLE (table), 2);
303 gtk_box_pack_start (GTK_BOX (main_vbox), table, FALSE, FALSE, 0);
304 gtk_widget_show (table);
305
306 size_data = gimp_scale_entry_new (GTK_TABLE (table), 0, 0,
307 _("_Displacement:"), SCALE_WIDTH, 0,
308 bvals.angledsp, 1, 90, 1, 15, 0,
309 TRUE, 0, 0,
310 NULL, NULL);
311 g_signal_connect (size_data, "value-changed",
312 G_CALLBACK (gimp_int_adjustment_update),
313 &bvals.angledsp);
314 g_signal_connect_swapped (size_data, "value-changed",
315 G_CALLBACK (gimp_preview_invalidate),
316 preview);
317
318 size_data = gimp_scale_entry_new (GTK_TABLE (table), 0, 1,
319 _("_Number of segments:"), SCALE_WIDTH, 0,
320 bvals.numsegs, 1, MAX_FANS, 1, 2, 0,
321 TRUE, 0, 0,
322 NULL, NULL);
323 g_signal_connect (size_data, "value-changed",
324 G_CALLBACK (gimp_int_adjustment_update),
325 &bvals.numsegs);
326 g_signal_connect_swapped (size_data, "value-changed",
327 G_CALLBACK (gimp_preview_invalidate),
328 preview);
329
330 gtk_widget_show (dialog);
331
332 run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
333
334 gtk_widget_destroy (dialog);
335
336 return run;
337 }
338
339 static void
blindsapply(guchar * srow,guchar * drow,gint width,gint bpp,guchar * bg)340 blindsapply (guchar *srow,
341 guchar *drow,
342 gint width,
343 gint bpp,
344 guchar *bg)
345 {
346 guchar *src;
347 guchar *dst;
348 gint i,j,k;
349 gdouble ang;
350 gint available;
351
352 /* Make the row 'shrink' around points along its length */
353 /* The bvals.numsegs determines how many segments to slip it in to */
354 /* The angle is the conceptual 'rotation' of each of these segments */
355
356 /* Note the row is considered to be made up of a two dim array actual
357 * pixel locations and the RGB color at these locations.
358 */
359
360 /* In the process copy the src row to the destination row */
361
362 /* Fill in with background color ? */
363 for (i = 0 ; i < width ; i++)
364 {
365 dst = &drow[i*bpp];
366
367 for (j = 0 ; j < bpp; j++)
368 {
369 dst[j] = bg[j];
370 }
371 }
372
373 /* Apply it */
374
375 available = width;
376 for (i = 0; i < bvals.numsegs; i++)
377 {
378 /* Width of segs are variable */
379 fanwidths[i] = available / (bvals.numsegs - i);
380 available -= fanwidths[i];
381 }
382
383 /* do center points first - just for fun...*/
384 available = 0;
385 for (k = 1; k <= bvals.numsegs; k++)
386 {
387 int point;
388
389 point = available + fanwidths[k - 1] / 2;
390
391 available += fanwidths[k - 1];
392
393 src = &srow[point * bpp];
394 dst = &drow[point * bpp];
395
396 /* Copy pixels across */
397 for (j = 0 ; j < bpp; j++)
398 {
399 dst[j] = src[j];
400 }
401 }
402
403 /* Disp for each point */
404 ang = (bvals.angledsp * 2 * G_PI) / 360; /* Angle in rads */
405 ang = (1 - fabs (cos (ang)));
406
407 available = 0;
408 for (k = 0 ; k < bvals.numsegs; k++)
409 {
410 int dx; /* Amount to move by */
411 int fw;
412
413 for (i = 0 ; i < (fanwidths[k]/2) ; i++)
414 {
415 /* Copy pixels across of left half of fan */
416 fw = fanwidths[k] / 2;
417 dx = (int) (ang * ((double) (fw - (double)(i % fw))));
418
419 src = &srow[(available + i) * bpp];
420 dst = &drow[(available + i + dx) * bpp];
421
422 for (j = 0; j < bpp; j++)
423 {
424 dst[j] = src[j];
425 }
426
427 /* Right side */
428 j = i + 1;
429 src = &srow[(available + fanwidths[k] - j
430 - (fanwidths[k] % 2)) * bpp];
431 dst = &drow[(available + fanwidths[k] - j
432 - (fanwidths[k] % 2) - dx) * bpp];
433
434 for (j = 0; j < bpp; j++)
435 {
436 dst[j] = src[j];
437 }
438 }
439
440 available += fanwidths[k];
441 }
442 }
443
444 static void
dialog_update_preview(gpointer drawable_ptr,GimpPreview * preview)445 dialog_update_preview (gpointer drawable_ptr,
446 GimpPreview *preview)
447 {
448 gint32 drawable_id = GPOINTER_TO_INT (drawable_ptr);
449 gint y;
450 guchar *p, *buffer, *cache;
451 GimpRGB background;
452 guchar bg[4];
453 gint width, height, bpp;
454
455 gimp_preview_get_size (preview, &width, &height);
456 cache = gimp_drawable_get_thumbnail_data (drawable_id,
457 &width, &height, &bpp);
458 p = cache;
459
460 gimp_context_get_background (&background);
461
462 if (bvals.bg_trans)
463 gimp_rgb_set_alpha (&background, 0.0);
464
465 if (gimp_drawable_is_gray (drawable_id))
466 {
467 bg[0] = gimp_rgb_luminance_uchar (&background);
468 gimp_rgba_get_uchar (&background, NULL, NULL, NULL, bg + 3);
469 }
470 else
471 {
472 gimp_rgba_get_uchar (&background, bg, bg + 1, bg + 2, bg + 3);
473 }
474
475 buffer = g_new (guchar, width * height * bpp);
476
477 if (bvals.orientation == GIMP_ORIENTATION_VERTICAL)
478 {
479 for (y = 0; y < height; y++)
480 {
481 blindsapply (p,
482 buffer + y * width * bpp,
483 width,
484 bpp, bg);
485 p += width * bpp;
486 }
487 }
488 else
489 {
490 /* Horizontal blinds */
491 /* Apply the blinds algo to a single column -
492 * this act as a transfomation matrix for the
493 * rows. Make row 0 invalid so we can find it again!
494 */
495 gint i;
496 guchar *sr = g_new (guchar, height * 4);
497 guchar *dr = g_new0 (guchar, height * 4);
498 guchar dummybg[4] = {0, 0, 0, 0};
499
500 /* Fill in with background color ? */
501 for (i = 0 ; i < width ; i++)
502 {
503 gint j;
504 gint bd = bpp;
505 guchar *dst;
506 dst = &buffer[i * bd];
507
508 for (j = 0 ; j < bd; j++)
509 {
510 dst[j] = bg[j];
511 }
512 }
513
514 for ( y = 0 ; y < height; y++)
515 {
516 sr[y] = y+1;
517 }
518
519 /* Bit of a fiddle since blindsapply really works on an image
520 * row not a set of bytes. - preview can't be > 255
521 * or must make dr sr int rows.
522 */
523 blindsapply (sr, dr, height, 1, dummybg);
524
525 for (y = 0; y < height; y++)
526 {
527 if (dr[y] == 0)
528 {
529 /* Draw background line */
530 p = buffer;
531 }
532 else
533 {
534 /* Draw line from src */
535 p = cache +
536 (width * bpp * (dr[y] - 1));
537 }
538 memcpy (buffer + y * width * bpp,
539 p,
540 width * bpp);
541 }
542 g_free (sr);
543 g_free (dr);
544 }
545
546 gimp_preview_draw_buffer (preview, buffer, width * bpp);
547
548 g_free (buffer);
549 g_free (cache);
550 }
551
552 /* STEP tells us how many rows/columns to gulp down in one go... */
553 /* Note all the "4" literals around here are to do with the depths
554 * of the images. Makes it easier to deal with for my small brain.
555 */
556
557 #define STEP 40
558
559 static void
apply_blinds(gint32 drawable_id)560 apply_blinds (gint32 drawable_id)
561 {
562 GeglBuffer *src_buffer;
563 GeglBuffer *dest_buffer;
564 const Babl *format;
565 guchar *src_rows, *des_rows;
566 gint bytes;
567 gint x, y;
568 GimpRGB background;
569 guchar bg[4];
570 gint sel_x1, sel_y1;
571 gint sel_width, sel_height;
572
573 gimp_context_get_background (&background);
574
575 if (bvals.bg_trans)
576 gimp_rgb_set_alpha (&background, 0.0);
577
578 gimp_rgba_get_uchar (&background, bg, bg + 1, bg + 2, bg + 3);
579
580 if (! gimp_drawable_mask_intersect (drawable_id,
581 &sel_x1, &sel_y1,
582 &sel_width, &sel_height))
583 return;
584
585 if (gimp_drawable_has_alpha (drawable_id))
586 format = babl_format ("R'G'B'A u8");
587 else
588 format = babl_format ("R'G'B' u8");
589
590 bytes = babl_format_get_bytes_per_pixel (format);
591
592 src_buffer = gimp_drawable_get_buffer (drawable_id);
593 dest_buffer = gimp_drawable_get_shadow_buffer (drawable_id);
594
595 src_rows = g_new (guchar, MAX (sel_width, sel_height) * bytes * STEP);
596 des_rows = g_new (guchar, MAX (sel_width, sel_height) * bytes * STEP);
597
598 if (bvals.orientation == GIMP_ORIENTATION_VERTICAL)
599 {
600 for (y = 0; y < sel_height; y += STEP)
601 {
602 gint rr;
603 gint step;
604
605 if ((y + STEP) > sel_height)
606 step = sel_height - y;
607 else
608 step = STEP;
609
610 gegl_buffer_get (src_buffer,
611 GEGL_RECTANGLE (sel_x1, sel_y1 + y,
612 sel_width, step), 1.0,
613 format, src_rows,
614 GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
615
616 /* OK I could make this better */
617 for (rr = 0; rr < STEP; rr++)
618 blindsapply (src_rows + (sel_width * rr * bytes),
619 des_rows + (sel_width * rr * bytes),
620 sel_width, bytes, bg);
621
622 gegl_buffer_set (dest_buffer,
623 GEGL_RECTANGLE (sel_x1, sel_y1 + y,
624 sel_width, step), 0,
625 format, des_rows,
626 GEGL_AUTO_ROWSTRIDE);
627
628 gimp_progress_update ((double) y / (double) sel_height);
629 }
630 }
631 else
632 {
633 /* Horizontal blinds */
634 /* Apply the blinds algo to a single column -
635 * this act as a transfomation matrix for the
636 * rows. Make row 0 invalid so we can find it again!
637 */
638 gint i;
639 gint *sr = g_new (gint, sel_height * bytes);
640 gint *dr = g_new (gint, sel_height * bytes);
641 guchar *dst = g_new (guchar, STEP * bytes);
642 guchar dummybg[4];
643
644 memset (dummybg, 0, 4);
645 memset (dr, 0, sel_height * bytes); /* all dr rows are background rows */
646 for (y = 0; y < sel_height; y++)
647 {
648 sr[y] = y+1;
649 }
650
651 /* Hmmm. does this work portably? */
652 /* This "swaps" the integers around that are held in in the
653 * sr & dr arrays.
654 */
655 blindsapply ((guchar *) sr, (guchar *) dr,
656 sel_height, sizeof (gint), dummybg);
657
658 /* Fill in with background color ? */
659 for (i = 0 ; i < STEP ; i++)
660 {
661 int j;
662 guchar *bgdst;
663 bgdst = &dst[i * bytes];
664
665 for (j = 0 ; j < bytes; j++)
666 {
667 bgdst[j] = bg[j];
668 }
669 }
670
671 for (x = 0; x < sel_width; x += STEP)
672 {
673 int rr;
674 int step;
675 guchar *p;
676
677 if((x + STEP) > sel_width)
678 step = sel_width - x;
679 else
680 step = STEP;
681
682 gegl_buffer_get (src_buffer,
683 GEGL_RECTANGLE (sel_x1 + x, sel_y1,
684 step, sel_height), 1.0,
685 format, src_rows,
686 GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
687
688 /* OK I could make this better */
689 for (rr = 0; rr < sel_height; rr++)
690 {
691 if(dr[rr] == 0)
692 {
693 /* Draw background line */
694 p = dst;
695 }
696 else
697 {
698 /* Draw line from src */
699 p = src_rows + (step * bytes * (dr[rr] - 1));
700 }
701 memcpy (des_rows + (rr * step * bytes), p,
702 step * bytes);
703 }
704
705 gegl_buffer_set (dest_buffer,
706 GEGL_RECTANGLE (sel_x1 + x, sel_y1,
707 step, sel_height), 0,
708 format, des_rows,
709 GEGL_AUTO_ROWSTRIDE);
710
711 gimp_progress_update ((double) x / (double) sel_width);
712 }
713
714 g_free (dst);
715 g_free (sr);
716 g_free (dr);
717 }
718
719 g_free (src_rows);
720 g_free (des_rows);
721
722 g_object_unref (src_buffer);
723 g_object_unref (dest_buffer);
724
725 gimp_progress_update (1.0);
726
727 gimp_drawable_merge_shadow (drawable_id, TRUE);
728 gimp_drawable_update (drawable_id,
729 sel_x1, sel_y1, sel_width, sel_height);
730 }
731