diff options
Diffstat (limited to 'plug-ins/gimpressionist/brush.c')
-rw-r--r-- | plug-ins/gimpressionist/brush.c | 660 |
1 files changed, 660 insertions, 0 deletions
diff --git a/plug-ins/gimpressionist/brush.c b/plug-ins/gimpressionist/brush.c new file mode 100644 index 0000000..a5d1e59 --- /dev/null +++ b/plug-ins/gimpressionist/brush.c @@ -0,0 +1,660 @@ +/* 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 <https://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include <string.h> + +#include <libgimp/gimp.h> +#include <libgimp/gimpui.h> + +#include <libgimpmath/gimpmath.h> + +#include <gtk/gtklist.h> +#include <gtk/gtkpreview.h> + +#include "gimpressionist.h" +#include "ppmtool.h" +#include "brush.h" +#include "presets.h" + +#include <libgimp/stdplugins-intl.h> + + +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); +} + |