1 /* GIMP - The GNU Image Manipulation Program
2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3  *
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
16  */
17 
18 /* Cartoon filter for GIMP for BIPS
19  *  -Spencer Kimball
20  *
21  * This filter propagates dark values in an image based on
22  *  each pixel's relative darkness to a neighboring average
23  */
24 
25 #include "config.h"
26 
27 #include <string.h>
28 
29 #include <libgimp/gimp.h>
30 #include <libgimp/gimpui.h>
31 
32 #include "libgimp/stdplugins-intl.h"
33 
34 
35 /* Some useful macros */
36 
37 #define PLUG_IN_PROC    "plug-in-cartoon"
38 #define PLUG_IN_BINARY  "cartoon"
39 #define PLUG_IN_ROLE    "gimp-cartoon"
40 #define TILE_CACHE_SIZE 48
41 
42 typedef struct
43 {
44   gdouble  mask_radius;
45   gdouble  threshold;
46   gdouble  pct_black;
47 } CartoonVals;
48 
49 
50 /*
51  * Function prototypes.
52  */
53 
54 static void      query  (void);
55 static void      run    (const gchar       *name,
56                          gint               nparams,
57                          const GimpParam   *param,
58                          gint              *nreturn_vals,
59                          GimpParam        **return_vals);
60 
61 static void      cartoon        (GimpDrawable *drawable,
62                                  GimpPreview  *preview);
63 static gboolean  cartoon_dialog (GimpDrawable *drawable);
64 
65 static gdouble   compute_ramp   (guchar       *dest1,
66                                  guchar       *dest2,
67                                  gint          length,
68                                  gdouble       pct_black);
69 
70 /*
71  * Gaussian blur helper functions
72  */
73 static void      find_constants    (gdouble  n_p[],
74                                     gdouble  n_m[],
75                                     gdouble  d_p[],
76                                     gdouble  d_m[],
77                                     gdouble  bd_p[],
78                                     gdouble  bd_m[],
79                                     gdouble  std_dev);
80 static void      transfer_pixels   (gdouble *src1,
81                                     gdouble *src2,
82                                     guchar  *dest,
83                                     gint     jump,
84                                     gint     bytes,
85                                     gint     width);
86 
87 
88 /***** Local vars *****/
89 
90 const GimpPlugInInfo PLUG_IN_INFO =
91 {
92   NULL,  /* init  */
93   NULL,  /* quit  */
94   query, /* query */
95   run,   /* run   */
96 };
97 
98 static CartoonVals cvals =
99 {
100   7.0,  /* mask_radius */
101   1.0,  /* threshold */
102   0.2   /* pct_black */
103 };
104 
105 
106 /***** Functions *****/
107 
MAIN()108 MAIN ()
109 
110 static void
111 query (void)
112 {
113   static const GimpParamDef args[] =
114   {
115     { GIMP_PDB_INT32,    "run-mode",    "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
116     { GIMP_PDB_IMAGE,    "image",       "Input image (unused)" },
117     { GIMP_PDB_DRAWABLE, "drawable",    "Input drawable" },
118     { GIMP_PDB_FLOAT,    "mask-radius", "Cartoon mask radius (radius of pixel neighborhood)" },
119     { GIMP_PDB_FLOAT,    "pct-black",   "Percentage of darkened pixels to set to black (0.0 - 1.0)" }
120   };
121 
122   gchar *help_string =
123     "Propagates dark values in an image based on "
124     "each pixel's relative darkness to a neighboring average. The idea behind "
125     "this filter is to give the look of a black felt pen drawing subsequently "
126     "shaded with color. This is achieved by darkening areas of the image which "
127     "are measured to be darker than a neighborhood average. In this way, "
128     "sufficiently large shifts in intensity are darkened to black. The rate "
129     "at which they are darkened to black is determined by the second pct_black "
130     "parameter. The mask_radius parameter controls the size of the pixel "
131     "neighborhood over which the average intensity is computed and then "
132     "compared to each pixel in the neighborhood to decide whether or not to "
133     "darken it to black. Large values for mask_radius result in very thick "
134     "black areas bordering the shaded regions of color and much less detail "
135     "for black areas everywhere including inside regions of color. Small "
136     "values result in more subtle pen strokes and detail everywhere. Small "
137     "values for the pct_black make the blend from the color regions to the "
138     "black border lines smoother and the lines themselves thinner and less "
139     "noticeable; larger values achieve the opposite effect.";
140 
141   gimp_install_procedure (PLUG_IN_PROC,
142                           N_("Simulate a cartoon by enhancing edges"),
143                           help_string,
144                           "Spencer Kimball",
145                           "Bit Specialists, Inc.",
146                           "2001",
147                           N_("Ca_rtoon (legacy)..."),
148                           "RGB*, GRAY*",
149                           GIMP_PLUGIN,
150                           G_N_ELEMENTS (args), 0,
151                           args, NULL);
152 
153   gimp_plugin_menu_register (PLUG_IN_PROC, "<Image>/Filters/Artistic");
154 }
155 
156 static void
run(const gchar * name,gint nparams,const GimpParam * param,gint * nreturn_vals,GimpParam ** return_vals)157 run (const gchar      *name,
158      gint              nparams,
159      const GimpParam  *param,
160      gint             *nreturn_vals,
161      GimpParam       **return_vals)
162 {
163   static GimpParam   values[2];
164   GimpRunMode        run_mode;
165   GimpDrawable      *drawable;
166   GimpPDBStatusType  status = GIMP_PDB_SUCCESS;
167 
168   run_mode = param[0].data.d_int32;
169 
170   /*  Get the specified drawable  */
171   drawable = gimp_drawable_get (param[2].data.d_drawable);
172 
173   /*  set the tile cache size  */
174   gimp_tile_cache_ntiles (TILE_CACHE_SIZE);
175 
176   *nreturn_vals = 1;
177   *return_vals  = values;
178 
179   values[0].type          = GIMP_PDB_STATUS;
180   values[0].data.d_status = status;
181 
182   INIT_I18N();
183 
184   switch (run_mode)
185     {
186     case GIMP_RUN_INTERACTIVE:
187       /*  Possibly retrieve data  */
188       gimp_get_data (PLUG_IN_PROC, &cvals);
189 
190       /*  First acquire information with a dialog  */
191       if (! cartoon_dialog (drawable))
192         return;
193       break;
194 
195     case GIMP_RUN_NONINTERACTIVE:
196       cvals.mask_radius = param[3].data.d_float;
197       cvals.pct_black   = param[4].data.d_float;
198       break;
199 
200     case GIMP_RUN_WITH_LAST_VALS:
201       /*  Possibly retrieve data  */
202       gimp_get_data (PLUG_IN_PROC, &cvals);
203       break;
204 
205     default:
206       break;
207     }
208 
209   if (status == GIMP_PDB_SUCCESS)
210     {
211       /*  Make sure that the drawable is RGB or GRAY color  */
212       if (gimp_drawable_is_rgb (drawable->drawable_id) ||
213           gimp_drawable_is_gray (drawable->drawable_id))
214         {
215           gimp_progress_init ("Cartoon");
216 
217 
218           cartoon (drawable, NULL);
219 
220           if (run_mode != GIMP_RUN_NONINTERACTIVE)
221             gimp_displays_flush ();
222 
223           /*  Store data  */
224           if (run_mode == GIMP_RUN_INTERACTIVE)
225             gimp_set_data (PLUG_IN_PROC, &cvals, sizeof (CartoonVals));
226         }
227       else
228         {
229           status        = GIMP_PDB_EXECUTION_ERROR;
230           *nreturn_vals = 2;
231           values[1].type          = GIMP_PDB_STRING;
232           values[1].data.d_string = _("Cannot operate on indexed color images.");
233         }
234     }
235 
236   values[0].data.d_status = status;
237 
238   gimp_drawable_detach (drawable);
239 }
240 
241 /*
242  * Cartoon algorithm
243  * -----------------
244  * Mask radius = radius of pixel neighborhood for intensity comparison
245  * Threshold   = relative intensity difference which will result in darkening
246  * Ramp        = amount of relative intensity difference before total black
247  * Blur radius = mask radius / 3.0
248  *
249  * Algorithm:
250  * For each pixel, calculate pixel intensity value to be: avg (blur radius)
251  * relative diff = pixel intensity / avg (mask radius)
252  * If relative diff < Threshold
253  *   intensity mult = (Ramp - MIN (Ramp, (Threshold - relative diff))) / Ramp
254  *   pixel intensity *= intensity mult
255  */
256 static void
cartoon(GimpDrawable * drawable,GimpPreview * preview)257 cartoon (GimpDrawable *drawable,
258          GimpPreview  *preview)
259 {
260   GimpPixelRgn  src_rgn, dest_rgn;
261   GimpPixelRgn *pr;
262   gint          x, y, width, height;
263   gint          bytes;
264   gboolean      has_alpha;
265   guchar       *dest1;
266   guchar       *dest2;
267   guchar       *src;
268   guchar       *src1, *sp_p1, *sp_m1;
269   guchar       *src2, *sp_p2, *sp_m2;
270   gdouble       n_p1[5], n_m1[5];
271   gdouble       n_p2[5], n_m2[5];
272   gdouble       d_p1[5], d_m1[5];
273   gdouble       d_p2[5], d_m2[5];
274   gdouble       bd_p1[5], bd_m1[5];
275   gdouble       bd_p2[5], bd_m2[5];
276   gdouble      *val_p1, *val_m1, *vp1, *vm1;
277   gdouble      *val_p2, *val_m2, *vp2, *vm2;
278   gint          i, j;
279   gint          row, col, b;
280   gint          terms;
281   gint          progress, max_progress;
282   gint          initial_p1[4];
283   gint          initial_p2[4];
284   gint          initial_m1[4];
285   gint          initial_m2[4];
286   gdouble       radius;
287   gdouble       std_dev1;
288   gdouble       std_dev2;
289   gdouble       ramp;
290   guchar       *preview_buffer = NULL;
291 
292   if (preview)
293     {
294       gimp_preview_get_position (preview, &x, &y);
295       gimp_preview_get_size (preview, &width, &height);
296     }
297   else if (! gimp_drawable_mask_intersect (drawable->drawable_id,
298                                            &x, &y, &width, &height))
299     {
300       return;
301     }
302 
303   bytes     = drawable->bpp;
304   has_alpha = gimp_drawable_has_alpha (drawable->drawable_id);
305 
306   val_p1 = g_new (gdouble, MAX (width, height) * bytes);
307   val_p2 = g_new (gdouble, MAX (width, height) * bytes);
308   val_m1 = g_new (gdouble, MAX (width, height) * bytes);
309   val_m2 = g_new (gdouble, MAX (width, height) * bytes);
310 
311   src   = g_new (guchar, MAX (width, height) * bytes);
312   dest1 = g_new0 (guchar, width * height);
313   dest2 = g_new0 (guchar, width * height);
314 
315   gimp_pixel_rgn_init (&src_rgn, drawable,
316                        0, 0, drawable->width, drawable->height, FALSE, FALSE);
317 
318   progress = 0;
319   max_progress = width * height * 2;
320 
321   /*  Calculate the standard deviations  */
322   radius   = 1.0; /* blur radius */
323   radius   = fabs (radius) + 1.0;
324   std_dev1 = sqrt (-(radius * radius) / (2 * log (1.0 / 255.0)));
325 
326   radius   = cvals.mask_radius;
327   radius   = fabs (radius) + 1.0;
328   std_dev2 = sqrt (-(radius * radius) / (2 * log (1.0 / 255.0)));
329 
330   /*  derive the constants for calculating the gaussian from the std dev  */
331   find_constants (n_p1, n_m1, d_p1, d_m1, bd_p1, bd_m1, std_dev1);
332   find_constants (n_p2, n_m2, d_p2, d_m2, bd_p2, bd_m2, std_dev2);
333 
334   /*  First the vertical pass  */
335   for (col = 0; col < width; col++)
336     {
337       memset (val_p1, 0, height * bytes * sizeof (gdouble));
338       memset (val_p2, 0, height * bytes * sizeof (gdouble));
339       memset (val_m1, 0, height * bytes * sizeof (gdouble));
340       memset (val_m2, 0, height * bytes * sizeof (gdouble));
341 
342       gimp_pixel_rgn_get_col (&src_rgn, src, col + x, y, height);
343 
344       src1  = src;
345       sp_p1 = src1;
346       sp_m1 = src1 + (height - 1) * bytes;
347       vp1   = val_p1;
348       vp2   = val_p2;
349       vm1   = val_m1 + (height - 1) * bytes;
350       vm2   = val_m2 + (height - 1) * bytes;
351 
352       /*  Set up the first vals  */
353       for (i = 0; i < bytes; i++)
354         {
355           initial_p1[i] = sp_p1[i];
356           initial_m1[i] = sp_m1[i];
357         }
358 
359       for (row = 0; row < height; row++)
360         {
361           gdouble *vpptr1, *vmptr1;
362           gdouble *vpptr2, *vmptr2;
363 
364           terms = (row < 4) ? row : 4;
365 
366           for (b = 0; b < bytes; b++)
367             {
368               vpptr1 = vp1 + b; vmptr1 = vm1 + b;
369               vpptr2 = vp2 + b; vmptr2 = vm2 + b;
370 
371               for (i = 0; i <= terms; i++)
372                 {
373                   *vpptr1 += n_p1[i] * sp_p1[(-i * bytes) + b] -
374                     d_p1[i] * vp1[(-i * bytes) + b];
375                   *vmptr1 += n_m1[i] * sp_m1[(i * bytes) + b] -
376                     d_m1[i] * vm1[(i * bytes) + b];
377 
378                   *vpptr2 += n_p2[i] * sp_p1[(-i * bytes) + b] -
379                     d_p2[i] * vp2[(-i * bytes) + b];
380                   *vmptr2 += n_m2[i] * sp_m1[(i * bytes) + b] -
381                     d_m2[i] * vm2[(i * bytes) + b];
382                 }
383 
384               for (j = i; j <= 4; j++)
385                 {
386                   *vpptr1 += (n_p1[j] - bd_p1[j]) * initial_p1[b];
387                   *vmptr1 += (n_m1[j] - bd_m1[j]) * initial_m1[b];
388 
389                   *vpptr2 += (n_p2[j] - bd_p2[j]) * initial_p1[b];
390                   *vmptr2 += (n_m2[j] - bd_m2[j]) * initial_m1[b];
391                 }
392             }
393 
394           sp_p1 += bytes;
395           sp_m1 -= bytes;
396           vp1   += bytes;
397           vp2   += bytes;
398           vm1   -= bytes;
399           vm2   -= bytes;
400         }
401 
402       transfer_pixels (val_p1, val_m1, dest1 + col, width, bytes, height);
403       transfer_pixels (val_p2, val_m2, dest2 + col, width, bytes, height);
404 
405       if (!preview)
406         {
407           progress += height;
408           if ((col % 5) == 0)
409             gimp_progress_update ((gdouble) progress / (gdouble) max_progress);
410         }
411     }
412 
413   for (row = 0; row < height; row++)
414     {
415       memset (val_p1, 0, width * sizeof (gdouble));
416       memset (val_p2, 0, width * sizeof (gdouble));
417       memset (val_m1, 0, width * sizeof (gdouble));
418       memset (val_m2, 0, width * sizeof (gdouble));
419 
420       src1 = dest1 + row * width;
421       src2 = dest2 + row * width;
422 
423       sp_p1 = src1;
424       sp_p2 = src2;
425       sp_m1 = src1 + width - 1;
426       sp_m2 = src2 + width - 1;
427       vp1   = val_p1;
428       vp2   = val_p2;
429       vm1   = val_m1 + width - 1;
430       vm2   = val_m2 + width - 1;
431 
432       /*  Set up the first vals  */
433       initial_p1[0] = sp_p1[0];
434       initial_p2[0] = sp_p2[0];
435       initial_m1[0] = sp_m1[0];
436       initial_m2[0] = sp_m2[0];
437 
438       for (col = 0; col < width; col++)
439         {
440           gdouble *vpptr1, *vmptr1;
441           gdouble *vpptr2, *vmptr2;
442 
443           terms = (col < 4) ? col : 4;
444 
445           vpptr1 = vp1; vmptr1 = vm1;
446           vpptr2 = vp2; vmptr2 = vm2;
447 
448           for (i = 0; i <= terms; i++)
449             {
450               *vpptr1 += n_p1[i] * sp_p1[-i] - d_p1[i] * vp1[-i];
451               *vmptr1 += n_m1[i] * sp_m1[i] - d_m1[i] * vm1[i];
452 
453               *vpptr2 += n_p2[i] * sp_p2[-i] - d_p2[i] * vp2[-i];
454               *vmptr2 += n_m2[i] * sp_m2[i] - d_m2[i] * vm2[i];
455             }
456 
457           for (j = i; j <= 4; j++)
458             {
459               *vpptr1 += (n_p1[j] - bd_p1[j]) * initial_p1[0];
460               *vmptr1 += (n_m1[j] - bd_m1[j]) * initial_m1[0];
461 
462               *vpptr2 += (n_p2[j] - bd_p2[j]) * initial_p2[0];
463               *vmptr2 += (n_m2[j] - bd_m2[j]) * initial_m2[0];
464             }
465 
466           sp_p1 ++;
467           sp_p2 ++;
468           sp_m1 --;
469           sp_m2 --;
470           vp1 ++;
471           vp2 ++;
472           vm1 --;
473           vm2 --;
474         }
475 
476       transfer_pixels (val_p1, val_m1, dest1 + row * width, 1, 1, width);
477       transfer_pixels (val_p2, val_m2, dest2 + row * width, 1, 1, width);
478 
479       if (!preview)
480         {
481           progress += width;
482           if ((row % 5) == 0)
483             gimp_progress_update ((gdouble) progress / (gdouble) max_progress);
484         }
485     }
486 
487   /* Compute the ramp value which sets 'pct_black' % of the darkened pixels black */
488   ramp = compute_ramp (dest1, dest2, width * height, cvals.pct_black);
489 
490   /* Initialize the pixel regions. */
491   gimp_pixel_rgn_init (&src_rgn, drawable, x, y, width, height, FALSE, FALSE);
492 
493   if (preview)
494     {
495       pr = gimp_pixel_rgns_register (1, &src_rgn);
496       preview_buffer = g_new (guchar, width * height * bytes);
497     }
498   else
499     {
500       gimp_pixel_rgn_init (&dest_rgn, drawable,
501                            x, y, width, height, TRUE, TRUE);
502       pr = gimp_pixel_rgns_register (2, &src_rgn, &dest_rgn);
503     }
504 
505   while (pr)
506     {
507       guchar  *src_ptr  = src_rgn.data;
508       guchar  *dest_ptr;
509       guchar  *blur_ptr = dest1 + (src_rgn.y - y) * width + (src_rgn.x - x);
510       guchar  *avg_ptr  = dest2 + (src_rgn.y - y) * width + (src_rgn.x - x);
511       gdouble  diff;
512       gdouble  mult     = 0.0;
513       gdouble  lightness;
514 
515       if (preview)
516         dest_ptr =
517           preview_buffer +
518           ((src_rgn.y - y) * width + (src_rgn.x - x)) * bytes;
519       else
520         dest_ptr = dest_rgn.data;
521 
522       for (row = 0; row < src_rgn.h; row++)
523         {
524           for (col = 0; col < src_rgn.w; col++)
525             {
526               if (avg_ptr[col] != 0)
527                 {
528                   diff = (gdouble) blur_ptr[col] / (gdouble) avg_ptr[col];
529                   if (diff < cvals.threshold)
530                     {
531                       if (ramp == 0.0)
532                         mult = 0.0;
533                       else
534                         mult = (ramp - MIN (ramp, (cvals.threshold - diff))) / ramp;
535                     }
536                   else
537                     mult = 1.0;
538                 }
539 
540               lightness = CLAMP (blur_ptr[col] * mult, 0, 255);
541 
542               if (bytes < 3)
543                 {
544                   dest_ptr[col * bytes] = (guchar) lightness;
545                   if (has_alpha)
546                     dest_ptr[col * bytes + 1] = src_ptr[col * src_rgn.bpp + 1];
547                 }
548               else
549                 {
550                   /*  Convert to HLS, set lightness and convert back  */
551                   gint r, g, b;
552 
553                   r = src_ptr[col * src_rgn.bpp + 0];
554                   g = src_ptr[col * src_rgn.bpp + 1];
555                   b = src_ptr[col * src_rgn.bpp + 2];
556 
557                   gimp_rgb_to_hsl_int (&r, &g, &b);
558                   b = lightness;
559                   gimp_hsl_to_rgb_int (&r, &g, &b);
560 
561                   dest_ptr[col * bytes + 0] = r;
562                   dest_ptr[col * bytes + 1] = g;
563                   dest_ptr[col * bytes + 2] = b;
564 
565                   if (has_alpha)
566                     dest_ptr[col * bytes + 3] = src_ptr[col * src_rgn.bpp + 3];
567                 }
568             }
569 
570           src_ptr  += src_rgn.rowstride;
571           if (preview)
572             dest_ptr += width * bytes;
573           else
574             dest_ptr += dest_rgn.rowstride;
575           blur_ptr += width;
576           avg_ptr  += width;
577         }
578 
579       if (!preview)
580         {
581           progress += src_rgn.w * src_rgn.h;
582           gimp_progress_update ((gdouble) progress / (gdouble) max_progress);
583         }
584 
585       pr = gimp_pixel_rgns_process (pr);
586     }
587 
588   if (preview)
589     {
590       gimp_preview_draw_buffer (preview, preview_buffer, width * bytes);
591       g_free (preview_buffer);
592     }
593   else
594     {
595       gimp_progress_update (1.0);
596       /*  merge the shadow, update the drawable  */
597       gimp_drawable_flush (drawable);
598       gimp_drawable_merge_shadow (drawable->drawable_id, TRUE);
599       gimp_drawable_update (drawable->drawable_id, x, y, width, height);
600     }
601 
602   /*  free up buffers  */
603   g_free (val_p1);
604   g_free (val_p2);
605   g_free (val_m1);
606   g_free (val_m2);
607   g_free (src);
608   g_free (dest1);
609   g_free (dest2);
610 }
611 
612 static gdouble
compute_ramp(guchar * dest1,guchar * dest2,gint length,gdouble pct_black)613 compute_ramp (guchar  *dest1,
614               guchar  *dest2,
615               gint     length,
616               gdouble  pct_black)
617 {
618   gint    hist[100];
619   gdouble diff;
620   gint    count;
621   gint    i;
622   gint    sum;
623 
624   memset (hist, 0, sizeof (int) * 100);
625   count = 0;
626 
627   for (i = 0; i < length; i++)
628     {
629       if (*dest2 != 0)
630         {
631           diff = (gdouble) *dest1 / (gdouble) *dest2;
632           if (diff < 1.0)
633             {
634               hist[(int) (diff * 100)] += 1;
635               count += 1;
636             }
637         }
638 
639       dest1++;
640       dest2++;
641     }
642 
643   if (pct_black == 0.0 || count == 0)
644     return 1.0;
645 
646   sum = 0;
647   for (i = 0; i < 100; i++)
648     {
649       sum += hist[i];
650       if (((gdouble) sum / (gdouble) count) > pct_black)
651         return (1.0 - (gdouble) i / 100.0);
652     }
653 
654   return 0.0;
655 }
656 
657 
658 /*
659  *  Gaussian blur helper functions
660  */
661 
662 static void
transfer_pixels(gdouble * src1,gdouble * src2,guchar * dest,gint jump,gint bytes,gint width)663 transfer_pixels (gdouble *src1,
664                  gdouble *src2,
665                  guchar  *dest,
666                  gint     jump,
667                  gint     bytes,
668                  gint     width)
669 {
670   gint    i, b;
671   gdouble sum[4];
672 
673   for(i = 0; i < width; i++)
674     {
675       for (b = 0; b < bytes; b++)
676         {
677           sum[b] = src1[b] + src2[b];
678           if (sum[b] > 255) sum[b] = 255;
679           else if(sum[b] < 0) sum[b] = 0;
680         }
681 
682       /*  Convert to lightness if RGB  */
683       if (bytes > 2)
684         *dest = (guchar) gimp_rgb_to_l_int (sum[0], sum[1], sum[2]);
685       else
686         *dest = (guchar) sum[0];
687 
688       src1 += bytes;
689       src2 += bytes;
690       dest += jump;
691     }
692 }
693 
694 static void
find_constants(gdouble n_p[],gdouble n_m[],gdouble d_p[],gdouble d_m[],gdouble bd_p[],gdouble bd_m[],gdouble std_dev)695 find_constants (gdouble n_p[],
696                 gdouble n_m[],
697                 gdouble d_p[],
698                 gdouble d_m[],
699                 gdouble bd_p[],
700                 gdouble bd_m[],
701                 gdouble std_dev)
702 {
703   gint    i;
704   gdouble constants [8];
705   gdouble div;
706 
707   /*  The constants used in the implementation of a casual sequence
708    *  using a 4th order approximation of the gaussian operator
709    */
710 
711   div = sqrt (2 * G_PI) * std_dev;
712 
713   constants [0] = -1.783  / std_dev;
714   constants [1] = -1.723  / std_dev;
715   constants [2] =  0.6318 / std_dev;
716   constants [3] =  1.997  / std_dev;
717   constants [4] =  1.6803 / div;
718   constants [5] =  3.735  / div;
719   constants [6] = -0.6803 / div;
720   constants [7] = -0.2598 / div;
721 
722   n_p [0] = constants[4] + constants[6];
723   n_p [1] = exp (constants[1]) *
724     (constants[7] * sin (constants[3]) -
725      (constants[6] + 2 * constants[4]) * cos (constants[3])) +
726        exp (constants[0]) *
727          (constants[5] * sin (constants[2]) -
728           (2 * constants[6] + constants[4]) * cos (constants[2]));
729   n_p [2] = 2 * exp (constants[0] + constants[1]) *
730     ((constants[4] + constants[6]) * cos (constants[3]) * cos (constants[2]) -
731      constants[5] * cos (constants[3]) * sin (constants[2]) -
732      constants[7] * cos (constants[2]) * sin (constants[3])) +
733        constants[6] * exp (2 * constants[0]) +
734          constants[4] * exp (2 * constants[1]);
735   n_p [3] = exp (constants[1] + 2 * constants[0]) *
736     (constants[7] * sin (constants[3]) - constants[6] * cos (constants[3])) +
737       exp (constants[0] + 2 * constants[1]) *
738         (constants[5] * sin (constants[2]) - constants[4] * cos (constants[2]));
739   n_p [4] = 0.0;
740 
741   d_p [0] = 0.0;
742   d_p [1] = -2 * exp (constants[1]) * cos (constants[3]) -
743     2 * exp (constants[0]) * cos (constants[2]);
744   d_p [2] = 4 * cos (constants[3]) * cos (constants[2]) * exp (constants[0] + constants[1]) +
745     exp (2 * constants[1]) + exp (2 * constants[0]);
746   d_p [3] = -2 * cos (constants[2]) * exp (constants[0] + 2 * constants[1]) -
747     2 * cos (constants[3]) * exp (constants[1] + 2 * constants[0]);
748   d_p [4] = exp (2 * constants[0] + 2 * constants[1]);
749 
750 #ifndef ORIGINAL_READABLE_CODE
751   memcpy(d_m, d_p, 5 * sizeof(gdouble));
752 #else
753   for (i = 0; i <= 4; i++)
754     d_m [i] = d_p [i];
755 #endif
756 
757   n_m[0] = 0.0;
758   for (i = 1; i <= 4; i++)
759     n_m [i] = n_p[i] - d_p[i] * n_p[0];
760 
761   {
762     gdouble sum_n_p, sum_n_m, sum_d;
763     gdouble a, b;
764 
765     sum_n_p = 0.0;
766     sum_n_m = 0.0;
767     sum_d   = 0.0;
768 
769     for (i = 0; i <= 4; i++)
770       {
771         sum_n_p += n_p[i];
772         sum_n_m += n_m[i];
773         sum_d += d_p[i];
774       }
775 
776 #ifndef ORIGINAL_READABLE_CODE
777     sum_d++;
778     a = sum_n_p / sum_d;
779     b = sum_n_m / sum_d;
780 #else
781     a = sum_n_p / (1 + sum_d);
782     b = sum_n_m / (1 + sum_d);
783 #endif
784 
785     for (i = 0; i <= 4; i++)
786       {
787         bd_p[i] = d_p[i] * a;
788         bd_m[i] = d_m[i] * b;
789       }
790   }
791 }
792 
793 /*******************************************************/
794 /*                    Dialog                           */
795 /*******************************************************/
796 
797 static gboolean
cartoon_dialog(GimpDrawable * drawable)798 cartoon_dialog (GimpDrawable *drawable)
799 {
800   GtkWidget *dialog;
801   GtkWidget *main_vbox;
802   GtkWidget *preview;
803   GtkWidget *table;
804   GtkObject *scale_data;
805   gboolean   run;
806 
807   gimp_ui_init (PLUG_IN_BINARY, FALSE);
808 
809   dialog = gimp_dialog_new (_("Cartoon"), PLUG_IN_ROLE,
810                             NULL, 0,
811                             gimp_standard_help_func, PLUG_IN_PROC,
812 
813                             _("_Cancel"), GTK_RESPONSE_CANCEL,
814                             _("_OK"),     GTK_RESPONSE_OK,
815 
816                             NULL);
817 
818   gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
819                                            GTK_RESPONSE_OK,
820                                            GTK_RESPONSE_CANCEL,
821                                            -1);
822 
823   gimp_window_set_transient (GTK_WINDOW (dialog));
824 
825   main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
826   gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12);
827   gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
828                       main_vbox, TRUE, TRUE, 0);
829   gtk_widget_show (main_vbox);
830 
831   preview = gimp_drawable_preview_new_from_drawable_id (drawable->drawable_id);
832   gtk_box_pack_start (GTK_BOX (main_vbox), preview, TRUE, TRUE, 0);
833   gtk_widget_show (preview);
834 
835   g_signal_connect_swapped (preview, "invalidated",
836                             G_CALLBACK (cartoon),
837                             drawable);
838 
839   table = gtk_table_new (3, 3, FALSE);
840   gtk_table_set_col_spacings (GTK_TABLE (table), 6);
841   gtk_table_set_row_spacings (GTK_TABLE (table), 6);
842   gtk_box_pack_start (GTK_BOX (main_vbox), table, FALSE, FALSE, 0);
843   gtk_widget_show (table);
844 
845   /*  Label, scale, entry for cvals.amount  */
846   scale_data = gimp_scale_entry_new (GTK_TABLE (table), 0, 0,
847                                      _("_Mask radius:"), 100, 5,
848                                      cvals.mask_radius, 1.0, 50.0, 1, 5.0, 2,
849                                      TRUE, 0, 0,
850                                      NULL, NULL);
851 
852   g_signal_connect (scale_data, "value-changed",
853                     G_CALLBACK (gimp_double_adjustment_update),
854                     &cvals.mask_radius);
855   g_signal_connect_swapped (scale_data, "value-changed",
856                             G_CALLBACK (gimp_preview_invalidate),
857                             preview);
858 
859   /*  Label, scale, entry for cvals.amount  */
860   scale_data = gimp_scale_entry_new (GTK_TABLE (table), 0, 1,
861                                      _("_Percent black:"), 50, 5,
862                                      cvals.pct_black, 0.0, 1.0, 0.01, 0.1, 3,
863                                      TRUE, 0, 0,
864                                      NULL, NULL);
865 
866   g_signal_connect (scale_data, "value-changed",
867                     G_CALLBACK (gimp_double_adjustment_update),
868                     &cvals.pct_black);
869   g_signal_connect_swapped (scale_data, "value-changed",
870                             G_CALLBACK (gimp_preview_invalidate),
871                             preview);
872 
873   gtk_widget_show (dialog);
874 
875   run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
876 
877   gtk_widget_destroy (dialog);
878 
879   return run;
880 }
881