/* GIMP - The GNU Image Manipulation Program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include #include #include #include "gimpressionist.h" #include "ppmtool.h" #include "brush.h" #include "presets.h" #include static void update_brush_preview (const char *fn); static GtkWidget *brush_preview = NULL; static GtkListStore *brush_list_store = NULL; static GtkWidget *brush_list = NULL; static GtkAdjustment *brush_relief_adjust = NULL; static GtkAdjustment *brush_aspect_adjust = NULL; static GtkAdjustment *brush_gamma_adjust = NULL; static gboolean brush_dont_update = FALSE; static gchar *last_selected_brush = NULL; static gint brush_from_file = 2; static ppm_t brushppm = {0, 0, NULL}; void brush_restore (void) { reselect (brush_list, pcvals.selected_brush); gtk_adjustment_set_value (brush_gamma_adjust, pcvals.brushgamma); gtk_adjustment_set_value (brush_relief_adjust, pcvals.brush_relief); gtk_adjustment_set_value (brush_aspect_adjust, pcvals.brush_aspect); } void brush_store (void) { pcvals.brushgamma = gtk_adjustment_get_value (brush_gamma_adjust); } void brush_free (void) { g_free (last_selected_brush); } void brush_get_selected (ppm_t *p) { if (brush_from_file) brush_reload (pcvals.selected_brush, p); else ppm_copy (&brushppm, p); } static gboolean file_is_color (const char *fn) { return fn && strstr (fn, ".ppm"); } void set_colorbrushes (const gchar *fn) { pcvals.color_brushes = file_is_color (fn); } static const Babl * get_u8_format (gint32 drawable_id) { if (gimp_drawable_is_rgb (drawable_id)) { if (gimp_drawable_has_alpha (drawable_id)) return babl_format ("R'G'B'A u8"); else return babl_format ("R'G'B' u8"); } else { if (gimp_drawable_has_alpha (drawable_id)) return babl_format ("Y'A u8"); else return babl_format ("Y' u8"); } } static void brushdmenuselect (GtkWidget *widget, gpointer data) { GeglBuffer *src_buffer; const Babl *format; guchar *src_row; guchar *src; gint bpp; gint x, y; ppm_t *p; gint x1, y1, w, h; gint row; gint32 drawable_id; gint rowstride; gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (widget), &drawable_id); if (drawable_id == -1) return; if (brush_from_file == 2) return; /* Not finished GUI-building yet */ if (brush_from_file) { #if 0 unselectall (brush_list); #endif preset_save_button_set_sensitive (FALSE); } gtk_adjustment_set_value (brush_gamma_adjust, 1.0); gtk_adjustment_set_value (brush_aspect_adjust, 0.0); if (! gimp_drawable_mask_intersect (drawable_id, &x1, &y1, &w, &h)) return; format = get_u8_format (drawable_id); bpp = babl_format_get_bytes_per_pixel (format); ppm_kill (&brushppm); ppm_new (&brushppm, w, h); p = &brushppm; rowstride = p->width * 3; src_row = g_new (guchar, w * bpp); src_buffer = gimp_drawable_get_buffer (drawable_id); if (bpp == 3) { /* RGB */ gint bpr = w * 3; gint y2 = y1 + h; for (row = 0, y = y1; y < y2; row++, y++) { gegl_buffer_get (src_buffer, GEGL_RECTANGLE (x1, y, w, 1), 1.0, format, src_row, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); memcpy (p->col + row*rowstride, src_row, bpr); } } else { /* RGBA (bpp > 3) GrayA (bpp == 2) or Gray */ gboolean is_gray = ((bpp > 3) ? TRUE : FALSE); gint y2 = y1 + h; for (row = 0, y = y1; y < y2; row++, y++) { guchar *tmprow = p->col + row * rowstride; guchar *tmprow_ptr; gint x2 = x1 + w; gegl_buffer_get (src_buffer, GEGL_RECTANGLE (x1, y, w, 1), 1.0, format, src_row, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE); src = src_row; tmprow_ptr = tmprow; /* Possible micro-optimization here: * src_end = src + src_rgn.bpp * w); * for ( ; src < src_end ; src += src_rgn.bpp) */ for (x = x1; x < x2; x++) { *(tmprow_ptr++) = src[0]; *(tmprow_ptr++) = src[is_gray ? 1 : 0]; *(tmprow_ptr++) = src[is_gray ? 2 : 0]; src += bpp; } } } g_object_unref (src_buffer); g_free (src_row); if (bpp >= 3) pcvals.color_brushes = 1; else pcvals.color_brushes = 0; brush_from_file = 0; update_brush_preview (NULL); } #if 0 void dummybrushdmenuselect (GtkWidget *w, gpointer data) { ppm_kill (&brushppm); ppm_new (&brushppm, 10,10); brush_from_file = 0; update_brush_preview (NULL); } #endif static void brushlistrefresh (void) { gtk_list_store_clear (brush_list_store); readdirintolist ("Brushes", brush_list, NULL); } static void savebrush_response (GtkWidget *dialog, gint response_id, gpointer data) { if (response_id == GTK_RESPONSE_OK) { gchar *name = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); ppm_save (&brushppm, name); brushlistrefresh (); g_free (name); } gtk_widget_destroy (dialog); } static void savebrush (GtkWidget *wg, gpointer data) { GtkWidget *dialog = NULL; GList *thispath = parsepath (); gchar *path; if (! PPM_IS_INITED (&brushppm)) { g_message ( _("Can only save drawables!")); return; } dialog = gtk_file_chooser_dialog_new (_("Save Brush"), GTK_WINDOW (gtk_widget_get_toplevel (wg)), GTK_FILE_CHOOSER_ACTION_SAVE, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Save"), GTK_RESPONSE_OK, NULL); gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), GTK_RESPONSE_OK, GTK_RESPONSE_CANCEL, -1); gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE); path = g_build_filename ((gchar *)thispath->data, "Brushes", NULL); gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), path); g_free (path); g_signal_connect (dialog, "destroy", G_CALLBACK (gtk_widget_destroyed), &dialog); g_signal_connect (dialog, "response", G_CALLBACK (savebrush_response), NULL); gtk_widget_show (dialog); } static gboolean validdrawable (gint32 imageid, gint32 drawableid, gpointer data) { return (gimp_drawable_is_rgb (drawableid) || gimp_drawable_is_gray (drawableid)); } /* * This function caches the last result. Call it with fn as NULL, to * free the arguments. * */ void brush_reload (const gchar *fn, ppm_t *p) { static char lastfn[256] = ""; static ppm_t cache = {0, 0, NULL}; if (fn == NULL) { ppm_kill (&cache); lastfn[0] = '\0'; return; } if (strcmp (fn, lastfn)) { g_strlcpy (lastfn, fn, sizeof (lastfn)); ppm_kill (&cache); ppm_load (fn, &cache); } ppm_copy (&cache, p); set_colorbrushes (fn); } static void padbrush (ppm_t *p, gint width, gint height) { guchar black[3] = {0, 0, 0}; int left = (width - p->width) / 2; int right = (width - p->width) - left; int top = (height - p->height) / 2; int bottom = (height - p->height) - top; ppm_pad (p, left, right, top, bottom, black); } static void update_brush_preview (const gchar *fn) { gint i, j; guchar *preview_image; if (fn) brush_from_file = 1; preview_image = g_new0 (guchar, 100*100); if (!fn && brush_from_file) { /* preview_image is already initialized to our liking. */ } else { double sc; ppm_t p = {0, 0, NULL}; guchar gammatable[256]; int newheight; if (brush_from_file) brush_reload (fn, &p); else if (PPM_IS_INITED (&brushppm)) ppm_copy (&brushppm, &p); set_colorbrushes (fn); sc = gtk_adjustment_get_value (brush_gamma_adjust); if (sc != 1.0) for (i = 0; i < 256; i++) gammatable[i] = pow (i / 255.0, sc) * 255; else for (i = 0; i < 256; i++) gammatable[i] = i; newheight = p.height * pow (10, gtk_adjustment_get_value (brush_aspect_adjust)); sc = p.width > newheight ? p.width : newheight; sc = 100.0 / sc; resize_fast (&p, p.width*sc,newheight*sc); padbrush (&p, 100, 100); for (i = 0; i < 100; i++) { int k = i * p.width * 3; if (i < p.height) for (j = 0; j < p.width; j++) preview_image[i*100+j] = gammatable[p.col[k + j * 3]]; } ppm_kill (&p); } gimp_preview_area_draw (GIMP_PREVIEW_AREA (brush_preview), 0, 0, 100, 100, GIMP_GRAY_IMAGE, preview_image, 100); g_free (preview_image); } /* * "force" implies here to change the brush even if it was the same. * It is used for the initialization of the preview. * */ static void brush_select (GtkTreeSelection *selection, gboolean force) { GtkTreeIter iter; GtkTreeModel *model; gchar *fname = NULL; gchar *brush = NULL; if (brush_dont_update) goto cleanup; if (brush_from_file == 0) { update_brush_preview (NULL); goto cleanup; } if (gtk_tree_selection_get_selected (selection, &model, &iter)) { gtk_tree_model_get (model, &iter, 0, &brush, -1); /* Check if the same brush was selected twice, and if so * break. Otherwise, the brush gamma and stuff would have been * reset. * */ if (last_selected_brush == NULL) { last_selected_brush = g_strdup (brush); } else { if (!strcmp (last_selected_brush, brush)) { if (!force) { goto cleanup; } } else { g_free (last_selected_brush); last_selected_brush = g_strdup (brush); } } brush_dont_update = TRUE; gtk_adjustment_set_value (brush_gamma_adjust, 1.0); gtk_adjustment_set_value (brush_aspect_adjust, 0.0); brush_dont_update = FALSE; if (brush) { fname = g_build_filename ("Brushes", brush, NULL); g_strlcpy (pcvals.selected_brush, fname, sizeof (pcvals.selected_brush)); update_brush_preview (fname); } } cleanup: g_free (fname); g_free (brush); } static void brush_select_file (GtkTreeSelection *selection, gpointer data) { brush_from_file = 1; preset_save_button_set_sensitive (TRUE); brush_select (selection, FALSE); } static void brush_preview_size_allocate (GtkWidget *preview) { GtkTreeSelection *selection; selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (brush_list)); brush_select (selection, TRUE); } static void brush_asepct_adjust_cb (GtkWidget *w, gpointer data) { gimp_double_adjustment_update (GTK_ADJUSTMENT (w), data); update_brush_preview (pcvals.selected_brush); } void create_brushpage (GtkNotebook *notebook) { GtkWidget *box1, *box2, *box3, *thispage; GtkWidget *view; GtkWidget *tmpw, *table; GtkWidget *frame; GtkWidget *combo; GtkWidget *label; GtkSizeGroup *group; GtkTreeSelection *selection; label = gtk_label_new_with_mnemonic (_("_Brush")); thispage = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); gtk_container_set_border_width (GTK_CONTAINER (thispage), 12); gtk_widget_show (thispage); box1 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); gtk_box_pack_start (GTK_BOX (thispage), box1, TRUE,TRUE,0); gtk_widget_show (box1); view = create_one_column_list (box1, brush_select_file); brush_list = view; brush_list_store = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (view))); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view)); box2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); gtk_widget_show (box2); frame = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); gtk_box_pack_start (GTK_BOX (box2), frame, FALSE, FALSE, 0); gtk_widget_show (frame); brush_preview = tmpw = gimp_preview_area_new (); gtk_widget_set_size_request (brush_preview, 100, 100); gtk_container_add (GTK_CONTAINER (frame), tmpw); gtk_widget_show (tmpw); g_signal_connect (brush_preview, "size-allocate", G_CALLBACK (brush_preview_size_allocate), NULL); box3 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); gtk_box_pack_end (GTK_BOX (box2), box3, FALSE, FALSE,0); gtk_widget_show (box3); tmpw = gtk_label_new (_("Gamma:")); gtk_label_set_xalign (GTK_LABEL (tmpw), 0.0); gtk_box_pack_start (GTK_BOX (box3), tmpw, FALSE, FALSE,0); gtk_widget_show (tmpw); brush_gamma_adjust = GTK_ADJUSTMENT (gtk_adjustment_new (pcvals.brushgamma, 0.5, 3.0, 0.1, 0.1, 1.0)); tmpw = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, brush_gamma_adjust); gtk_widget_set_size_request (GTK_WIDGET (tmpw), 100, 30); gtk_scale_set_draw_value (GTK_SCALE (tmpw), FALSE); gtk_scale_set_digits (GTK_SCALE (tmpw), 2); gtk_box_pack_start (GTK_BOX (box3), tmpw, FALSE, FALSE, 0); gtk_widget_show (tmpw); g_signal_connect_swapped (brush_gamma_adjust, "value-changed", G_CALLBACK (update_brush_preview), pcvals.selected_brush); gimp_help_set_help_data (tmpw, _("Changes the gamma (brightness) of the selected brush"), NULL); box3 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); gtk_box_pack_start (GTK_BOX (thispage), box3, FALSE, FALSE,0); gtk_widget_show (box3); group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); tmpw = gtk_label_new (_("Select:")); gtk_label_set_xalign (GTK_LABEL (tmpw), 0.0); gtk_box_pack_start (GTK_BOX (box3), tmpw, FALSE, FALSE, 0); gtk_widget_show (tmpw); gtk_size_group_add_widget (group, tmpw); g_object_unref (group); combo = gimp_drawable_combo_box_new (validdrawable, NULL); gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo), -1, G_CALLBACK (brushdmenuselect), NULL); gtk_box_pack_start (GTK_BOX (box3), combo, TRUE, TRUE, 0); gtk_widget_show (combo); tmpw = gtk_button_new_with_mnemonic (_("Save _as")); gtk_box_pack_start (GTK_BOX (box3),tmpw, FALSE, FALSE, 0); g_signal_connect (tmpw, "clicked", G_CALLBACK (savebrush), NULL); gtk_widget_show (tmpw); table = gtk_table_new (2, 3, FALSE); gtk_table_set_col_spacings (GTK_TABLE (table), 6); gtk_table_set_row_spacings (GTK_TABLE (table), 6); gtk_box_pack_start (GTK_BOX (thispage), table, FALSE, FALSE, 0); gtk_widget_show (table); brush_aspect_adjust = (GtkAdjustment *) gimp_scale_entry_new (GTK_TABLE (table), 0, 0, _("Aspect ratio:"), 150, -1, pcvals.brush_aspect, -1.0, 1.0, 0.1, 0.1, 2, TRUE, 0, 0, _("Specifies the aspect ratio of the brush"), NULL); gtk_size_group_add_widget (group, GIMP_SCALE_ENTRY_LABEL (brush_aspect_adjust)); g_signal_connect (brush_aspect_adjust, "value-changed", G_CALLBACK (brush_asepct_adjust_cb), &pcvals.brush_aspect); brush_relief_adjust = (GtkAdjustment *) gimp_scale_entry_new (GTK_TABLE (table), 0, 1, _("Relief:"), 150, -1, pcvals.brush_relief, 0.0, 100.0, 1.0, 10.0, 1, TRUE, 0, 0, _("Specifies the amount of embossing to apply to the image (in percent)"), NULL); gtk_size_group_add_widget (group, GIMP_SCALE_ENTRY_LABEL (brush_relief_adjust)); g_signal_connect (brush_relief_adjust, "value-changed", G_CALLBACK (gimp_double_adjustment_update), &pcvals.brush_relief); brush_select (selection, FALSE); readdirintolist ("Brushes", view, pcvals.selected_brush); /* * This is so the "changed signal won't get sent to the brushes' list * and reset the gamma and stuff. * */ gtk_widget_grab_focus (brush_list); gtk_notebook_append_page_menu (notebook, thispage, label, NULL); }