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