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 #include "config.h"
19 
20 #include <string.h>
21 
22 #include <libgimp/gimp.h>
23 #include <libgimp/gimpui.h>
24 
25 #include <libgimpmath/gimpmath.h>
26 
27 #include <gtk/gtklist.h>
28 #include <gtk/gtkpreview.h>
29 
30 #include "gimpressionist.h"
31 #include "ppmtool.h"
32 #include "brush.h"
33 #include "presets.h"
34 
35 #include <libgimp/stdplugins-intl.h>
36 
37 
38 static void  update_brush_preview (const char *fn);
39 
40 
41 static GtkWidget    *brush_preview    = NULL;
42 static GtkListStore *brush_list_store = NULL;
43 
44 static GtkWidget     *brush_list          = NULL;
45 static GtkAdjustment *brush_relief_adjust = NULL;
46 static GtkAdjustment *brush_aspect_adjust = NULL;
47 static GtkAdjustment *brush_gamma_adjust  = NULL;
48 static gboolean       brush_dont_update   = FALSE;
49 
50 static gchar *last_selected_brush = NULL;
51 
52 static gint brush_from_file = 2;
53 
54 static ppm_t brushppm  = {0, 0, NULL};
55 
56 void
brush_restore(void)57 brush_restore (void)
58 {
59   reselect (brush_list, pcvals.selected_brush);
60   gtk_adjustment_set_value (brush_gamma_adjust, pcvals.brushgamma);
61   gtk_adjustment_set_value (brush_relief_adjust, pcvals.brush_relief);
62   gtk_adjustment_set_value (brush_aspect_adjust, pcvals.brush_aspect);
63 }
64 
65 void
brush_store(void)66 brush_store (void)
67 {
68   pcvals.brushgamma = gtk_adjustment_get_value (brush_gamma_adjust);
69 }
70 
71 void
brush_free(void)72 brush_free (void)
73 {
74   g_free (last_selected_brush);
75 }
76 
brush_get_selected(ppm_t * p)77 void brush_get_selected (ppm_t *p)
78 {
79   if (brush_from_file)
80     brush_reload (pcvals.selected_brush, p);
81   else
82     ppm_copy (&brushppm, p);
83 }
84 
85 
86 static gboolean
file_is_color(const char * fn)87 file_is_color (const char *fn)
88 {
89   return fn && strstr (fn, ".ppm");
90 }
91 
92 void
set_colorbrushes(const gchar * fn)93 set_colorbrushes (const gchar *fn)
94 {
95   pcvals.color_brushes = file_is_color (fn);
96 }
97 
98 static const Babl *
get_u8_format(gint32 drawable_id)99 get_u8_format (gint32 drawable_id)
100 {
101   if (gimp_drawable_is_rgb (drawable_id))
102     {
103       if (gimp_drawable_has_alpha (drawable_id))
104         return babl_format ("R'G'B'A u8");
105       else
106         return babl_format ("R'G'B' u8");
107     }
108   else
109     {
110       if (gimp_drawable_has_alpha (drawable_id))
111         return babl_format ("Y'A u8");
112       else
113         return babl_format ("Y' u8");
114     }
115 }
116 
117 static void
brushdmenuselect(GtkWidget * widget,gpointer data)118 brushdmenuselect (GtkWidget *widget,
119                   gpointer   data)
120 {
121   GeglBuffer *src_buffer;
122   const Babl *format;
123   guchar     *src_row;
124   guchar     *src;
125   gint        bpp;
126   gint        x, y;
127   ppm_t      *p;
128   gint        x1, y1, w, h;
129   gint        row;
130   gint32      drawable_id;
131   gint        rowstride;
132 
133   gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), &drawable_id);
134 
135   if (drawable_id == -1)
136     return;
137 
138   if (brush_from_file == 2)
139     return; /* Not finished GUI-building yet */
140 
141   if (brush_from_file)
142     {
143 #if 0
144       unselectall (brush_list);
145 #endif
146       preset_save_button_set_sensitive (FALSE);
147     }
148 
149   gtk_adjustment_set_value (brush_gamma_adjust, 1.0);
150   gtk_adjustment_set_value (brush_aspect_adjust, 0.0);
151 
152   if (! gimp_drawable_mask_intersect (drawable_id, &x1, &y1, &w, &h))
153     return;
154 
155   format = get_u8_format (drawable_id);
156   bpp    = babl_format_get_bytes_per_pixel (format);
157 
158   ppm_kill (&brushppm);
159   ppm_new (&brushppm, w, h);
160   p = &brushppm;
161 
162   rowstride = p->width * 3;
163 
164   src_row = g_new (guchar, w * bpp);
165 
166   src_buffer = gimp_drawable_get_buffer (drawable_id);
167 
168   if (bpp == 3)
169     { /* RGB */
170       gint bpr = w * 3;
171       gint y2 = y1 + h;
172 
173       for (row = 0, y = y1; y < y2; row++, y++)
174         {
175           gegl_buffer_get (src_buffer, GEGL_RECTANGLE (x1, y, w, 1), 1.0,
176                            format, src_row,
177                            GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
178 
179           memcpy (p->col + row*rowstride, src_row, bpr);
180         }
181     }
182   else
183     { /* RGBA (bpp > 3) GrayA (bpp == 2) or Gray */
184       gboolean is_gray = ((bpp > 3) ? TRUE : FALSE);
185       gint y2 = y1 + h;
186 
187       for (row = 0, y = y1; y < y2; row++, y++)
188         {
189           guchar *tmprow = p->col + row * rowstride;
190           guchar *tmprow_ptr;
191 	  gint x2 = x1 + w;
192 
193           gegl_buffer_get (src_buffer, GEGL_RECTANGLE (x1, y, w, 1), 1.0,
194                            format, src_row,
195                            GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
196 
197           src = src_row;
198           tmprow_ptr = tmprow;
199           /* Possible micro-optimization here:
200            * src_end = src + src_rgn.bpp * w);
201            * for ( ; src < src_end ; src += src_rgn.bpp)
202            */
203           for (x = x1; x < x2; x++)
204             {
205               *(tmprow_ptr++) = src[0];
206               *(tmprow_ptr++) = src[is_gray ? 1 : 0];
207               *(tmprow_ptr++) = src[is_gray ? 2 : 0];
208               src += bpp;
209             }
210         }
211     }
212 
213   g_object_unref (src_buffer);
214 
215   g_free (src_row);
216 
217   if (bpp >= 3)
218     pcvals.color_brushes = 1;
219   else
220     pcvals.color_brushes = 0;
221 
222   brush_from_file = 0;
223   update_brush_preview (NULL);
224 }
225 
226 #if 0
227 void
228 dummybrushdmenuselect (GtkWidget *w, gpointer data)
229 {
230   ppm_kill (&brushppm);
231   ppm_new (&brushppm, 10,10);
232   brush_from_file = 0;
233   update_brush_preview (NULL);
234 }
235 #endif
236 
237 static void
brushlistrefresh(void)238 brushlistrefresh (void)
239 {
240   gtk_list_store_clear (brush_list_store);
241   readdirintolist ("Brushes", brush_list, NULL);
242 }
243 
244 static void
savebrush_response(GtkWidget * dialog,gint response_id,gpointer data)245 savebrush_response (GtkWidget *dialog,
246                     gint       response_id,
247                     gpointer   data)
248 {
249   if (response_id == GTK_RESPONSE_OK)
250     {
251       gchar *name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
252 
253       ppm_save (&brushppm, name);
254       brushlistrefresh ();
255 
256       g_free (name);
257     }
258 
259   gtk_widget_destroy (dialog);
260 }
261 
262 static void
savebrush(GtkWidget * wg,gpointer data)263 savebrush (GtkWidget *wg,
264            gpointer   data)
265 {
266   GtkWidget *dialog   = NULL;
267   GList     *thispath = parsepath ();
268   gchar     *path;
269 
270   if (! PPM_IS_INITED (&brushppm))
271     {
272       g_message ( _("Can only save drawables!"));
273       return;
274     }
275 
276   dialog =
277     gtk_file_chooser_dialog_new (_("Save Brush"),
278                                  GTK_WINDOW (gtk_widget_get_toplevel (wg)),
279                                  GTK_FILE_CHOOSER_ACTION_SAVE,
280 
281                                  _("_Cancel"), GTK_RESPONSE_CANCEL,
282                                  _("_Save"),   GTK_RESPONSE_OK,
283 
284                                  NULL);
285 
286   gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
287   gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
288                                            GTK_RESPONSE_OK,
289                                            GTK_RESPONSE_CANCEL,
290                                            -1);
291 
292   gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog),
293                                                   TRUE);
294 
295   path = g_build_filename ((gchar *)thispath->data, "Brushes", NULL);
296 
297   gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), path);
298 
299   g_free (path);
300 
301   g_signal_connect (dialog, "destroy",
302                     G_CALLBACK (gtk_widget_destroyed),
303                     &dialog);
304   g_signal_connect (dialog, "response",
305                     G_CALLBACK (savebrush_response),
306                     NULL);
307 
308   gtk_widget_show (dialog);
309 }
310 
311 static gboolean
validdrawable(gint32 imageid,gint32 drawableid,gpointer data)312 validdrawable (gint32    imageid,
313                gint32    drawableid,
314                gpointer  data)
315 {
316   return (gimp_drawable_is_rgb (drawableid) ||
317           gimp_drawable_is_gray (drawableid));
318 }
319 
320 /*
321  * This function caches the last result. Call it with fn as NULL, to
322  * free the arguments.
323  * */
324 void
brush_reload(const gchar * fn,ppm_t * p)325 brush_reload (const gchar *fn,
326               ppm_t       *p)
327 {
328   static char  lastfn[256] = "";
329   static ppm_t cache       = {0, 0, NULL};
330 
331   if (fn == NULL)
332     {
333       ppm_kill (&cache);
334       lastfn[0] = '\0';
335       return;
336     }
337 
338   if (strcmp (fn, lastfn))
339     {
340       g_strlcpy (lastfn, fn, sizeof (lastfn));
341       ppm_kill (&cache);
342       ppm_load (fn, &cache);
343     }
344   ppm_copy (&cache, p);
345   set_colorbrushes (fn);
346 }
347 
348 static void
padbrush(ppm_t * p,gint width,gint height)349 padbrush (ppm_t *p,
350           gint   width,
351           gint   height)
352 {
353   guchar black[3] = {0, 0, 0};
354 
355   int left   = (width - p->width) / 2;
356   int right  = (width - p->width) - left;
357   int top    = (height - p->height) / 2;
358   int bottom = (height - p->height) - top;
359 
360   ppm_pad (p, left, right, top, bottom, black);
361 }
362 
363 static void
update_brush_preview(const gchar * fn)364 update_brush_preview (const gchar *fn)
365 {
366   gint   i, j;
367   guchar *preview_image;
368 
369   if (fn)
370     brush_from_file = 1;
371 
372   preview_image = g_new0 (guchar, 100*100);
373 
374   if (!fn && brush_from_file)
375     {
376       /* preview_image is already initialized to our liking. */
377     }
378   else
379     {
380       double sc;
381       ppm_t  p = {0, 0, NULL};
382       guchar gammatable[256];
383       int    newheight;
384 
385       if (brush_from_file)
386         brush_reload (fn, &p);
387       else if (PPM_IS_INITED (&brushppm))
388         ppm_copy (&brushppm, &p);
389 
390       set_colorbrushes (fn);
391 
392       sc = gtk_adjustment_get_value (brush_gamma_adjust);
393       if (sc != 1.0)
394         for (i = 0; i < 256; i++)
395           gammatable[i] = pow (i / 255.0, sc) * 255;
396       else
397         for (i = 0; i < 256; i++)
398           gammatable[i] = i;
399 
400       newheight = p.height *
401         pow (10, gtk_adjustment_get_value (brush_aspect_adjust));
402 
403       sc = p.width > newheight ? p.width : newheight;
404       sc = 100.0 / sc;
405       resize_fast (&p, p.width*sc,newheight*sc);
406       padbrush (&p, 100, 100);
407       for (i = 0; i < 100; i++)
408         {
409           int k = i * p.width * 3;
410           if (i < p.height)
411             for (j = 0; j < p.width; j++)
412               preview_image[i*100+j] = gammatable[p.col[k + j * 3]];
413         }
414       ppm_kill (&p);
415     }
416   gimp_preview_area_draw (GIMP_PREVIEW_AREA (brush_preview),
417                           0, 0, 100, 100,
418                           GIMP_GRAY_IMAGE,
419                           preview_image,
420                           100);
421 
422   g_free (preview_image);
423 }
424 
425 
426 /*
427  * "force" implies here to change the brush even if it was the same.
428  * It is used for the initialization of the preview.
429  * */
430 static void
brush_select(GtkTreeSelection * selection,gboolean force)431 brush_select (GtkTreeSelection *selection, gboolean force)
432 {
433   GtkTreeIter   iter;
434   GtkTreeModel *model;
435   gchar        *fname = NULL;
436   gchar        *brush = NULL;
437 
438   if (brush_dont_update)
439     goto cleanup;
440 
441   if (brush_from_file == 0)
442     {
443       update_brush_preview (NULL);
444       goto cleanup;
445     }
446 
447   if (gtk_tree_selection_get_selected (selection, &model, &iter))
448     {
449       gtk_tree_model_get (model, &iter, 0, &brush, -1);
450 
451       /* Check if the same brush was selected twice, and if so
452        * break. Otherwise, the brush gamma and stuff would have been
453        * reset.
454        * */
455       if (last_selected_brush == NULL)
456         {
457           last_selected_brush = g_strdup (brush);
458         }
459       else
460         {
461           if (!strcmp (last_selected_brush, brush))
462             {
463               if (!force)
464                 {
465                   goto cleanup;
466                 }
467             }
468           else
469             {
470               g_free (last_selected_brush);
471               last_selected_brush = g_strdup (brush);
472             }
473         }
474 
475       brush_dont_update = TRUE;
476       gtk_adjustment_set_value (brush_gamma_adjust, 1.0);
477       gtk_adjustment_set_value (brush_aspect_adjust, 0.0);
478       brush_dont_update = FALSE;
479 
480       if (brush)
481         {
482           fname = g_build_filename ("Brushes", brush, NULL);
483 
484           g_strlcpy (pcvals.selected_brush,
485                      fname, sizeof (pcvals.selected_brush));
486 
487           update_brush_preview (fname);
488 
489         }
490     }
491 cleanup:
492   g_free (fname);
493   g_free (brush);
494 }
495 
496 static void
brush_select_file(GtkTreeSelection * selection,gpointer data)497 brush_select_file (GtkTreeSelection *selection, gpointer data)
498 {
499   brush_from_file = 1;
500   preset_save_button_set_sensitive (TRUE);
501   brush_select (selection, FALSE);
502 }
503 
504 static void
brush_preview_size_allocate(GtkWidget * preview)505 brush_preview_size_allocate (GtkWidget *preview)
506 {
507   GtkTreeSelection *selection;
508 
509   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (brush_list));
510   brush_select (selection, TRUE);
511 }
512 
513 static void
brush_asepct_adjust_cb(GtkWidget * w,gpointer data)514 brush_asepct_adjust_cb (GtkWidget *w, gpointer data)
515 {
516   gimp_double_adjustment_update (GTK_ADJUSTMENT (w), data);
517   update_brush_preview (pcvals.selected_brush);
518 }
519 
520 void
create_brushpage(GtkNotebook * notebook)521 create_brushpage (GtkNotebook *notebook)
522 {
523   GtkWidget        *box1, *box2, *box3, *thispage;
524   GtkWidget        *view;
525   GtkWidget        *tmpw, *table;
526   GtkWidget        *frame;
527   GtkWidget        *combo;
528   GtkWidget        *label;
529   GtkSizeGroup     *group;
530   GtkTreeSelection *selection;
531 
532   label = gtk_label_new_with_mnemonic (_("_Brush"));
533 
534   thispage = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
535   gtk_container_set_border_width (GTK_CONTAINER (thispage), 12);
536   gtk_widget_show (thispage);
537 
538   box1 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
539   gtk_box_pack_start (GTK_BOX (thispage), box1, TRUE,TRUE,0);
540   gtk_widget_show (box1);
541 
542   view = create_one_column_list (box1, brush_select_file);
543   brush_list = view;
544   brush_list_store =
545       GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (view)));
546 
547   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
548 
549   box2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
550   gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
551   gtk_widget_show (box2);
552 
553   frame = gtk_frame_new (NULL);
554   gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
555   gtk_box_pack_start (GTK_BOX (box2), frame, FALSE, FALSE, 0);
556   gtk_widget_show (frame);
557 
558   brush_preview = tmpw = gimp_preview_area_new ();
559   gtk_widget_set_size_request (brush_preview, 100, 100);
560   gtk_container_add (GTK_CONTAINER (frame), tmpw);
561   gtk_widget_show (tmpw);
562   g_signal_connect (brush_preview, "size-allocate",
563                     G_CALLBACK (brush_preview_size_allocate), NULL);
564 
565   box3 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
566   gtk_box_pack_end (GTK_BOX (box2), box3, FALSE, FALSE,0);
567   gtk_widget_show (box3);
568 
569   tmpw = gtk_label_new (_("Gamma:"));
570   gtk_label_set_xalign (GTK_LABEL (tmpw), 0.0);
571   gtk_box_pack_start (GTK_BOX (box3), tmpw, FALSE, FALSE,0);
572   gtk_widget_show (tmpw);
573 
574   brush_gamma_adjust = GTK_ADJUSTMENT (gtk_adjustment_new (pcvals.brushgamma,
575                                                            0.5, 3.0, 0.1, 0.1, 1.0));
576   tmpw = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, brush_gamma_adjust);
577   gtk_widget_set_size_request (GTK_WIDGET (tmpw), 100, 30);
578   gtk_scale_set_draw_value (GTK_SCALE (tmpw), FALSE);
579   gtk_scale_set_digits (GTK_SCALE (tmpw), 2);
580   gtk_box_pack_start (GTK_BOX (box3), tmpw, FALSE, FALSE, 0);
581   gtk_widget_show (tmpw);
582   g_signal_connect_swapped (brush_gamma_adjust, "value-changed",
583                             G_CALLBACK (update_brush_preview),
584                             pcvals.selected_brush);
585 
586   gimp_help_set_help_data
587     (tmpw, _("Changes the gamma (brightness) of the selected brush"), NULL);
588 
589   box3 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
590   gtk_box_pack_start (GTK_BOX (thispage), box3, FALSE, FALSE,0);
591   gtk_widget_show (box3);
592 
593   group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
594 
595   tmpw = gtk_label_new (_("Select:"));
596   gtk_label_set_xalign (GTK_LABEL (tmpw), 0.0);
597   gtk_box_pack_start (GTK_BOX (box3), tmpw, FALSE, FALSE, 0);
598   gtk_widget_show (tmpw);
599 
600   gtk_size_group_add_widget (group, tmpw);
601   g_object_unref (group);
602 
603   combo = gimp_drawable_combo_box_new (validdrawable, NULL);
604   gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), -1,
605                               G_CALLBACK (brushdmenuselect),
606                               NULL);
607 
608   gtk_box_pack_start (GTK_BOX (box3), combo, TRUE, TRUE, 0);
609   gtk_widget_show (combo);
610 
611   tmpw = gtk_button_new_with_mnemonic (_("Save _as"));
612   gtk_box_pack_start (GTK_BOX (box3),tmpw, FALSE, FALSE, 0);
613   g_signal_connect (tmpw, "clicked", G_CALLBACK (savebrush), NULL);
614   gtk_widget_show (tmpw);
615 
616   table = gtk_table_new (2, 3, FALSE);
617   gtk_table_set_col_spacings (GTK_TABLE (table), 6);
618   gtk_table_set_row_spacings (GTK_TABLE (table), 6);
619   gtk_box_pack_start (GTK_BOX (thispage), table, FALSE, FALSE, 0);
620   gtk_widget_show (table);
621 
622   brush_aspect_adjust = (GtkAdjustment *)
623     gimp_scale_entry_new (GTK_TABLE (table), 0, 0,
624                           _("Aspect ratio:"),
625                           150, -1, pcvals.brush_aspect,
626                           -1.0, 1.0, 0.1, 0.1, 2,
627                           TRUE, 0, 0,
628                           _("Specifies the aspect ratio of the brush"),
629                           NULL);
630   gtk_size_group_add_widget (group,
631                              GIMP_SCALE_ENTRY_LABEL (brush_aspect_adjust));
632   g_signal_connect (brush_aspect_adjust, "value-changed",
633                     G_CALLBACK (brush_asepct_adjust_cb), &pcvals.brush_aspect);
634 
635   brush_relief_adjust = (GtkAdjustment *)
636     gimp_scale_entry_new (GTK_TABLE (table), 0, 1,
637                           _("Relief:"),
638                           150, -1, pcvals.brush_relief,
639                           0.0, 100.0, 1.0, 10.0, 1,
640                           TRUE, 0, 0,
641                           _("Specifies the amount of embossing to apply to the image (in percent)"),
642                           NULL);
643   gtk_size_group_add_widget (group,
644                              GIMP_SCALE_ENTRY_LABEL (brush_relief_adjust));
645   g_signal_connect (brush_relief_adjust, "value-changed",
646                     G_CALLBACK (gimp_double_adjustment_update),
647                     &pcvals.brush_relief);
648 
649   brush_select (selection, FALSE);
650   readdirintolist ("Brushes", view, pcvals.selected_brush);
651 
652   /*
653    * This is so the "changed signal won't get sent to the brushes' list
654    * and reset the gamma and stuff.
655    * */
656   gtk_widget_grab_focus (brush_list);
657 
658   gtk_notebook_append_page_menu (notebook, thispage, label, NULL);
659 }
660 
661