/* 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 "libgimpmath/gimpmath.h" #include "paint-types.h" #include "operations/layer-modes/gimp-layer-modes.h" #include "gegl/gimp-gegl-utils.h" #include "core/gimp-palettes.h" #include "core/gimpdrawable.h" #include "core/gimpimage.h" #include "core/gimpimage-undo.h" #include "core/gimppickable.h" #include "core/gimpsymmetry.h" #include "core/gimptempbuf.h" #include "gimpinkoptions.h" #include "gimpink.h" #include "gimpink-blob.h" #include "gimpinkundo.h" #include "gimp-intl.h" #define SUBSAMPLE 8 /* local function prototypes */ static void gimp_ink_finalize (GObject *object); static void gimp_ink_paint (GimpPaintCore *paint_core, GimpDrawable *drawable, GimpPaintOptions *paint_options, GimpSymmetry *sym, GimpPaintState paint_state, guint32 time); static GeglBuffer * gimp_ink_get_paint_buffer (GimpPaintCore *paint_core, GimpDrawable *drawable, GimpPaintOptions *paint_options, GimpLayerMode paint_mode, const GimpCoords *coords, gint *paint_buffer_x, gint *paint_buffer_y, gint *paint_width, gint *paint_height); static GimpUndo * gimp_ink_push_undo (GimpPaintCore *core, GimpImage *image, const gchar *undo_desc); static void gimp_ink_motion (GimpPaintCore *paint_core, GimpDrawable *drawable, GimpPaintOptions *paint_options, GimpSymmetry *sym, guint32 time); static GimpBlob * ink_pen_ellipse (GimpInkOptions *options, gdouble x_center, gdouble y_center, gdouble pressure, gdouble xtilt, gdouble ytilt, gdouble velocity, const GimpMatrix3 *transform); static void render_blob (GeglBuffer *buffer, GeglRectangle *rect, GimpBlob *blob); G_DEFINE_TYPE (GimpInk, gimp_ink, GIMP_TYPE_PAINT_CORE) #define parent_class gimp_ink_parent_class void gimp_ink_register (Gimp *gimp, GimpPaintRegisterCallback callback) { (* callback) (gimp, GIMP_TYPE_INK, GIMP_TYPE_INK_OPTIONS, "gimp-ink", _("Ink"), "gimp-tool-ink"); } static void gimp_ink_class_init (GimpInkClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GimpPaintCoreClass *paint_core_class = GIMP_PAINT_CORE_CLASS (klass); object_class->finalize = gimp_ink_finalize; paint_core_class->paint = gimp_ink_paint; paint_core_class->get_paint_buffer = gimp_ink_get_paint_buffer; paint_core_class->push_undo = gimp_ink_push_undo; } static void gimp_ink_init (GimpInk *ink) { } static void gimp_ink_finalize (GObject *object) { GimpInk *ink = GIMP_INK (object); if (ink->start_blobs) { g_list_free_full (ink->start_blobs, g_free); ink->start_blobs = NULL; } if (ink->last_blobs) { g_list_free_full (ink->last_blobs, g_free); ink->last_blobs = NULL; } G_OBJECT_CLASS (parent_class)->finalize (object); } static void gimp_ink_paint (GimpPaintCore *paint_core, GimpDrawable *drawable, GimpPaintOptions *paint_options, GimpSymmetry *sym, GimpPaintState paint_state, guint32 time) { GimpInk *ink = GIMP_INK (paint_core); GimpCoords *cur_coords; GimpCoords last_coords; gimp_paint_core_get_last_coords (paint_core, &last_coords); cur_coords = gimp_symmetry_get_origin (sym); switch (paint_state) { case GIMP_PAINT_STATE_INIT: { GimpContext *context = GIMP_CONTEXT (paint_options); GimpRGB foreground; gimp_symmetry_set_stateful (sym, TRUE); gimp_context_get_foreground (context, &foreground); gimp_palettes_add_color_history (context->gimp, &foreground); if (cur_coords->x == last_coords.x && cur_coords->y == last_coords.y) { if (ink->start_blobs) { g_list_free_full (ink->start_blobs, g_free); ink->start_blobs = NULL; } if (ink->last_blobs) { g_list_free_full (ink->last_blobs, g_free); ink->last_blobs = NULL; } } else if (ink->last_blobs) { GimpBlob *last_blob; GList *iter; gint i; if (ink->start_blobs) { g_list_free_full (ink->start_blobs, g_free); ink->start_blobs = NULL; } /* save the start blobs of each stroke for undo otherwise */ for (iter = ink->last_blobs, i = 0; iter; iter = g_list_next (iter), i++) { last_blob = g_list_nth_data (ink->last_blobs, i); ink->start_blobs = g_list_prepend (ink->start_blobs, gimp_blob_duplicate (last_blob)); } ink->start_blobs = g_list_reverse (ink->start_blobs); } } break; case GIMP_PAINT_STATE_MOTION: gimp_ink_motion (paint_core, drawable, paint_options, sym, time); break; case GIMP_PAINT_STATE_FINISH: gimp_symmetry_set_stateful (sym, FALSE); break; } } static GeglBuffer * gimp_ink_get_paint_buffer (GimpPaintCore *paint_core, GimpDrawable *drawable, GimpPaintOptions *paint_options, GimpLayerMode paint_mode, const GimpCoords *coords, gint *paint_buffer_x, gint *paint_buffer_y, gint *paint_width, gint *paint_height) { GimpInk *ink = GIMP_INK (paint_core); gint x, y; gint width, height; gint dwidth, dheight; gint x1, y1, x2, y2; gimp_blob_bounds (ink->cur_blob, &x, &y, &width, &height); dwidth = gimp_item_get_width (GIMP_ITEM (drawable)); dheight = gimp_item_get_height (GIMP_ITEM (drawable)); x1 = CLAMP (x / SUBSAMPLE - 1, 0, dwidth); y1 = CLAMP (y / SUBSAMPLE - 1, 0, dheight); x2 = CLAMP ((x + width) / SUBSAMPLE + 2, 0, dwidth); y2 = CLAMP ((y + height) / SUBSAMPLE + 2, 0, dheight); if (paint_width) *paint_width = width / SUBSAMPLE + 3; if (paint_height) *paint_height = height / SUBSAMPLE + 3; /* configure the canvas buffer */ if ((x2 - x1) && (y2 - y1)) { GimpTempBuf *temp_buf; const Babl *format; GimpLayerCompositeMode composite_mode; composite_mode = gimp_layer_mode_get_paint_composite_mode (paint_mode); format = gimp_layer_mode_get_format (paint_mode, GIMP_LAYER_COLOR_SPACE_AUTO, GIMP_LAYER_COLOR_SPACE_AUTO, composite_mode, gimp_drawable_get_format (drawable)); temp_buf = gimp_temp_buf_new ((x2 - x1), (y2 - y1), format); *paint_buffer_x = x1; *paint_buffer_y = y1; if (paint_core->paint_buffer) g_object_unref (paint_core->paint_buffer); paint_core->paint_buffer = gimp_temp_buf_create_buffer (temp_buf); gimp_temp_buf_unref (temp_buf); return paint_core->paint_buffer; } return NULL; } static GimpUndo * gimp_ink_push_undo (GimpPaintCore *core, GimpImage *image, const gchar *undo_desc) { return gimp_image_undo_push (image, GIMP_TYPE_INK_UNDO, GIMP_UNDO_INK, undo_desc, 0, "paint-core", core, NULL); } static void gimp_ink_motion (GimpPaintCore *paint_core, GimpDrawable *drawable, GimpPaintOptions *paint_options, GimpSymmetry *sym, guint32 time) { GimpInk *ink = GIMP_INK (paint_core); GimpInkOptions *options = GIMP_INK_OPTIONS (paint_options); GimpContext *context = GIMP_CONTEXT (paint_options); GList *blob_unions = NULL; GList *blobs_to_render = NULL; GeglBuffer *paint_buffer; gint paint_buffer_x; gint paint_buffer_y; GimpLayerMode paint_mode; GimpRGB foreground; GeglColor *color; GimpBlob *last_blob; GimpCoords *coords; gint n_strokes; gint i; n_strokes = gimp_symmetry_get_size (sym); if (ink->last_blobs && g_list_length (ink->last_blobs) != n_strokes) { g_list_free_full (ink->last_blobs, g_free); ink->last_blobs = NULL; } if (! ink->last_blobs) { if (ink->start_blobs) { g_list_free_full (ink->start_blobs, g_free); ink->start_blobs = NULL; } for (i = 0; i < n_strokes; i++) { GimpMatrix3 transform; coords = gimp_symmetry_get_coords (sym, i); gimp_symmetry_get_matrix (sym, i, &transform); last_blob = ink_pen_ellipse (options, coords->x, coords->y, coords->pressure, coords->xtilt, coords->ytilt, 100, &transform); ink->last_blobs = g_list_prepend (ink->last_blobs, last_blob); ink->start_blobs = g_list_prepend (ink->start_blobs, gimp_blob_duplicate (last_blob)); blobs_to_render = g_list_prepend (blobs_to_render, last_blob); } ink->start_blobs = g_list_reverse (ink->start_blobs); ink->last_blobs = g_list_reverse (ink->last_blobs); blobs_to_render = g_list_reverse (blobs_to_render); } else { for (i = 0; i < n_strokes; i++) { GimpBlob *blob; GimpBlob *blob_union = NULL; GimpMatrix3 transform; coords = gimp_symmetry_get_coords (sym, i); gimp_symmetry_get_matrix (sym, i, &transform); blob = ink_pen_ellipse (options, coords->x, coords->y, coords->pressure, coords->xtilt, coords->ytilt, coords->velocity * 100, &transform); last_blob = g_list_nth_data (ink->last_blobs, i); blob_union = gimp_blob_convex_union (last_blob, blob); g_free (last_blob); g_list_nth (ink->last_blobs, i)->data = blob; blobs_to_render = g_list_prepend (blobs_to_render, blob_union); blob_unions = g_list_prepend (blob_unions, blob_union); } blobs_to_render = g_list_reverse (blobs_to_render); } paint_mode = gimp_context_get_paint_mode (context); gimp_context_get_foreground (context, &foreground); gimp_pickable_srgb_to_image_color (GIMP_PICKABLE (drawable), &foreground, &foreground); color = gimp_gegl_color_new (&foreground); for (i = 0; i < n_strokes; i++) { GimpBlob *blob_to_render = g_list_nth_data (blobs_to_render, i); coords = gimp_symmetry_get_coords (sym, i); ink->cur_blob = blob_to_render; paint_buffer = gimp_paint_core_get_paint_buffer (paint_core, drawable, paint_options, paint_mode, coords, &paint_buffer_x, &paint_buffer_y, NULL, NULL); ink->cur_blob = NULL; if (! paint_buffer) continue; gegl_buffer_set_color (paint_buffer, NULL, color); /* draw the blob directly to the canvas_buffer */ render_blob (paint_core->canvas_buffer, GEGL_RECTANGLE (paint_core->paint_buffer_x, paint_core->paint_buffer_y, gegl_buffer_get_width (paint_core->paint_buffer), gegl_buffer_get_height (paint_core->paint_buffer)), blob_to_render); /* draw the paint_area using the just rendered canvas_buffer as mask */ gimp_paint_core_paste (paint_core, NULL, paint_core->paint_buffer_x, paint_core->paint_buffer_y, drawable, GIMP_OPACITY_OPAQUE, gimp_context_get_opacity (context), paint_mode, GIMP_PAINT_CONSTANT); } g_object_unref (color); g_list_free_full (blob_unions, g_free); } static GimpBlob * ink_pen_ellipse (GimpInkOptions *options, gdouble x_center, gdouble y_center, gdouble pressure, gdouble xtilt, gdouble ytilt, gdouble velocity, const GimpMatrix3 *transform) { GimpBlobFunc blob_function; gdouble size; gdouble tsin, tcos; gdouble aspect, radmin; gdouble x,y; gdouble tscale; gdouble tscale_c; gdouble tscale_s; /* Adjust the size depending on pressure. */ size = options->size * (1.0 + options->size_sensitivity * (2.0 * pressure - 1.0)); /* Adjust the size further depending on pointer velocity and * velocity-sensitivity. These 'magic constants' are 'feels * natural' tigert-approved. --ADM */ if (velocity < 3.0) velocity = 3.0; #ifdef VERBOSE g_printerr ("%g (%g) -> ", size, velocity); #endif size = (options->vel_sensitivity * ((4.5 * size) / (1.0 + options->vel_sensitivity * (2.0 * velocity))) + (1.0 - options->vel_sensitivity) * size); #ifdef VERBOSE g_printerr ("%g\n", (gfloat) size); #endif /* Clamp resulting size to sane limits */ if (size > options->size * (1.0 + options->size_sensitivity)) size = options->size * (1.0 + options->size_sensitivity); if (size * SUBSAMPLE < 1.0) size = 1.0 / SUBSAMPLE; /* Add brush angle/aspect to tilt vectorially */ /* I'm not happy with the way the brush widget info is combined with * tilt info from the brush. My personal feeling is that * representing both as affine transforms would make the most * sense. -RLL */ tscale = options->tilt_sensitivity * 10.0; tscale_c = tscale * cos (gimp_deg_to_rad (options->tilt_angle)); tscale_s = tscale * sin (gimp_deg_to_rad (options->tilt_angle)); x = (options->blob_aspect * cos (options->blob_angle) + xtilt * tscale_c - ytilt * tscale_s); y = (options->blob_aspect * sin (options->blob_angle) + ytilt * tscale_c + xtilt * tscale_s); #ifdef VERBOSE g_printerr ("angle %g aspect %g; %g %g; %g %g\n", options->blob_angle, options->blob_aspect, tscale_c, tscale_s, x, y); #endif aspect = sqrt (SQR (x) + SQR (y)); if (aspect != 0) { tcos = x / aspect; tsin = y / aspect; } else { tcos = cos (options->blob_angle); tsin = sin (options->blob_angle); } gimp_matrix3_transform_point (transform, tcos, tsin, &tcos, &tsin); aspect = CLAMP (aspect, 1.0, 10.0); radmin = MAX (1.0, SUBSAMPLE * size / aspect); switch (options->blob_type) { case GIMP_INK_BLOB_TYPE_CIRCLE: blob_function = gimp_blob_ellipse; break; case GIMP_INK_BLOB_TYPE_SQUARE: blob_function = gimp_blob_square; break; case GIMP_INK_BLOB_TYPE_DIAMOND: blob_function = gimp_blob_diamond; break; default: g_return_val_if_reached (NULL); break; } return (* blob_function) (x_center * SUBSAMPLE, y_center * SUBSAMPLE, radmin * aspect * tcos, radmin * aspect * tsin, -radmin * tsin, radmin * tcos); } /*********************************/ /* Rendering functions */ /*********************************/ /* Some of this stuff should probably be combined with the * code it was copied from in paint_core.c; but I wanted * to learn this stuff, so I've kept it simple. * * The following only supports CONSTANT mode. Incremental * would, I think, interact strangely with the way we * do things. But it wouldn't be hard to implement at all. */ enum { ROW_START, ROW_STOP }; /* The insertion sort here, for SUBSAMPLE = 8, tends to beat out * qsort() by 4x with CFLAGS=-O2, 2x with CFLAGS=-g */ static void insert_sort (gint *data, gint n) { gint i, j, k; for (i = 2; i < 2 * n; i += 2) { gint tmp1 = data[i]; gint tmp2 = data[i + 1]; j = 0; while (data[j] < tmp1) j += 2; for (k = i; k > j; k -= 2) { data[k] = data[k - 2]; data[k + 1] = data[k - 1]; } data[j] = tmp1; data[j + 1] = tmp2; } } static void fill_run (gfloat *dest, gfloat alpha, gint w) { if (alpha == 1.0) { while (w--) { *dest = 1.0; dest++; } } else { while (w--) { *dest = MAX (*dest, alpha); dest++; } } } static void render_blob_line (GimpBlob *blob, gfloat *dest, gint x, gint y, gint width) { gint buf[4 * SUBSAMPLE]; gint *data = buf; gint n = 0; gint i, j; gint current = 0; /* number of filled rows at this point * in the scan line */ gint last_x; /* Sort start and ends for all lines */ j = y * SUBSAMPLE - blob->y; for (i = 0; i < SUBSAMPLE; i++) { if (j >= blob->height) break; if ((j > 0) && (blob->data[j].left <= blob->data[j].right)) { data[2 * n] = blob->data[j].left; data[2 * n + 1] = ROW_START; data[2 * SUBSAMPLE + 2 * n] = blob->data[j].right; data[2 * SUBSAMPLE + 2 * n + 1] = ROW_STOP; n++; } j++; } /* If we have less than SUBSAMPLE rows, compress */ if (n < SUBSAMPLE) { for (i = 0; i < 2 * n; i++) data[2 * n + i] = data[2 * SUBSAMPLE + i]; } /* Now count start and end separately */ n *= 2; insert_sort (data, n); /* Discard portions outside of tile */ while ((n > 0) && (data[0] < SUBSAMPLE*x)) { if (data[1] == ROW_START) current++; else current--; data += 2; n--; } while ((n > 0) && (data[2*(n-1)] >= SUBSAMPLE*(x+width))) n--; /* Render the row */ last_x = 0; for (i = 0; i < n;) { gint cur_x = data[2 * i] / SUBSAMPLE - x; gint pixel; /* Fill in portion leading up to this pixel */ if (current && cur_x != last_x) fill_run (dest + last_x, (gfloat) current / SUBSAMPLE, cur_x - last_x); /* Compute the value for this pixel */ pixel = current * SUBSAMPLE; while (iitems[0].roi; while (gegl_buffer_iterator_next (iter)) { gfloat *d = iter->items[0].data; gint h = roi->height; gint y; for (y = 0; y < h; y++, d += roi->width * 1) { render_blob_line (blob, d, roi->x, roi->y + y, roi->width); } } }