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