From 5b76d73d2cef7c61748289d6bb7b9180110ecea9 Mon Sep 17 00:00:00 2001 From: Alx Sa Date: Sat, 5 Jul 2025 03:21:56 +0000 Subject: [PATCH 1/3] core, paint, tools: Port to MyPaint Brushes2 This patch ports our MyPaint code to use the MyPaintSurface2 API, allowing us to support version 2 MyPaint Brushes correctly. The API update lets us take into account the zoom factor and rotation of the canvas when drawing. It also adds a "Gain" option to the GUI in order to control the strength of the input's pressure (tablet or mouse). As a caveat, this patch does not yet implement spectral/pigment blending. --- app/core/gimpmybrush-load.c | 21 +++- app/core/gimpmybrush-private.h | 4 + app/core/gimpmybrush.c | 45 ++++++- app/core/gimpmybrush.h | 4 + app/paint/gimpmybrushcore.c | 97 ++++++++++----- app/paint/gimpmybrushoptions.c | 100 ++++++++++++++- app/paint/gimpmybrushoptions.h | 7 ++ app/paint/gimpmybrushsurface.c | 192 +++++++++++++++++++++-------- app/tools/gimpmybrushoptions-gui.c | 5 + app/tools/gimpmybrushtool.c | 23 +++- 10 files changed, 403 insertions(+), 95 deletions(-) diff --git a/app/core/gimpmybrush-load.c b/app/core/gimpmybrush-load.c index 1dbb9f5bd77..891ce43a9a2 100644 --- a/app/core/gimpmybrush-load.c +++ b/app/core/gimpmybrush-load.c @@ -69,7 +69,7 @@ gimp_mybrush_load (GimpContext *context, G_FILE_ATTRIBUTE_STANDARD_SIZE); g_object_unref (info); - if (size > 32768) + if (size > G_MAXSHORT) { g_set_error (error, GIMP_DATA_ERROR, GIMP_DATA_ERROR_READ, _("MyPaint brush file is unreasonably large, skipping.")); @@ -84,7 +84,7 @@ gimp_mybrush_load (GimpContext *context, return NULL; } - mypaint_brush = mypaint_brush_new (); + mypaint_brush = mypaint_brush_new_with_buckets (64); mypaint_brush_from_defaults (mypaint_brush); if (! mypaint_brush_from_string (mypaint_brush, (const gchar *) buffer)) @@ -147,6 +147,23 @@ gimp_mybrush_load (GimpContext *context, mypaint_brush_get_base_value (mypaint_brush, MYPAINT_BRUSH_SETTING_OFFSET_BY_RANDOM); + brush->priv->gain = + mypaint_brush_get_base_value (mypaint_brush, + MYPAINT_BRUSH_SETTING_PRESSURE_GAIN_LOG); + + /* Version 2 MyPaint Brush options */ + brush->priv->pigment = + mypaint_brush_get_base_value (mypaint_brush, + MYPAINT_BRUSH_SETTING_PAINT_MODE); + + brush->priv->posterize = + mypaint_brush_get_base_value (mypaint_brush, + MYPAINT_BRUSH_SETTING_POSTERIZE); + + brush->priv->posterize_num = + mypaint_brush_get_base_value (mypaint_brush, + MYPAINT_BRUSH_SETTING_POSTERIZE_NUM); + mypaint_brush_unref (mypaint_brush); return g_list_prepend (NULL, brush); diff --git a/app/core/gimpmybrush-private.h b/app/core/gimpmybrush-private.h index 7966c0b20df..6fb0c77e18d 100644 --- a/app/core/gimpmybrush-private.h +++ b/app/core/gimpmybrush-private.h @@ -27,6 +27,10 @@ struct _GimpMybrushPrivate gdouble radius; gdouble opaque; gdouble hardness; + gdouble gain; + gdouble pigment; + gdouble posterize; + gdouble posterize_num; gdouble offset_by_random; gboolean eraser; }; diff --git a/app/core/gimpmybrush.c b/app/core/gimpmybrush.c index c4256873c3f..ee196bcf753 100644 --- a/app/core/gimpmybrush.c +++ b/app/core/gimpmybrush.c @@ -98,10 +98,14 @@ gimp_mybrush_init (GimpMybrush *brush) { brush->priv = gimp_mybrush_get_instance_private (brush); - brush->priv->radius = 1.0; - brush->priv->opaque = 1.0; - brush->priv->hardness = 1.0; - brush->priv->eraser = FALSE; + brush->priv->radius = 1.0; + brush->priv->opaque = 1.0; + brush->priv->hardness = 1.0; + brush->priv->gain = 0.0; + brush->priv->pigment = 0.0; + brush->priv->posterize = 0.0; + brush->priv->posterize_num = 1.0; + brush->priv->eraser = FALSE; } static void @@ -262,6 +266,39 @@ gimp_mybrush_get_hardness (GimpMybrush *brush) return brush->priv->hardness; } +gdouble +gimp_mybrush_get_gain (GimpMybrush *brush) +{ + g_return_val_if_fail (GIMP_IS_MYBRUSH (brush), 0.0); + + return brush->priv->gain; +} + + +gdouble +gimp_mybrush_get_pigment (GimpMybrush *brush) +{ + g_return_val_if_fail (GIMP_IS_MYBRUSH (brush), 0.0); + + return brush->priv->pigment; +} + +gdouble +gimp_mybrush_get_posterize (GimpMybrush *brush) +{ + g_return_val_if_fail (GIMP_IS_MYBRUSH (brush), 0.0); + + return brush->priv->posterize; +} + +gdouble +gimp_mybrush_get_posterize_num (GimpMybrush *brush) +{ + g_return_val_if_fail (GIMP_IS_MYBRUSH (brush), 0.05); + + return brush->priv->posterize_num; +} + gdouble gimp_mybrush_get_offset_by_random (GimpMybrush *brush) { diff --git a/app/core/gimpmybrush.h b/app/core/gimpmybrush.h index 2dad734fe1c..c54760b2492 100644 --- a/app/core/gimpmybrush.h +++ b/app/core/gimpmybrush.h @@ -59,6 +59,10 @@ const gchar * gimp_mybrush_get_brush_json (GimpMybrush *brush); gdouble gimp_mybrush_get_radius (GimpMybrush *brush); gdouble gimp_mybrush_get_opaque (GimpMybrush *brush); gdouble gimp_mybrush_get_hardness (GimpMybrush *brush); +gdouble gimp_mybrush_get_gain (GimpMybrush *brush); +gdouble gimp_mybrush_get_pigment (GimpMybrush *brush); +gdouble gimp_mybrush_get_posterize (GimpMybrush *brush); +gdouble gimp_mybrush_get_posterize_num (GimpMybrush *brush); gdouble gimp_mybrush_get_offset_by_random (GimpMybrush *brush); gboolean gimp_mybrush_get_is_eraser (GimpMybrush *brush); diff --git a/app/paint/gimpmybrushcore.c b/app/paint/gimpmybrushcore.c index aa1b9586576..ac823e5ba84 100644 --- a/app/paint/gimpmybrushcore.c +++ b/app/paint/gimpmybrushcore.c @@ -80,7 +80,10 @@ static void gimp_mybrush_core_motion (GimpPaintCore *paint_core GimpDrawable *drawable, GimpPaintOptions *paint_options, GimpSymmetry *sym, - guint32 time); + guint32 time, + gfloat view_zoom, + gfloat view_rotation, + gfloat barrel_rotation); static void gimp_mybrush_core_create_brushes (GimpMybrushCore *mybrush, GimpDrawable *drawable, GimpPaintOptions *paint_options, @@ -200,10 +203,11 @@ gimp_mybrush_core_paint (GimpPaintCore *paint_core, GimpPaintState paint_state, guint32 time) { - GimpMybrushCore *mybrush = GIMP_MYBRUSH_CORE (paint_core); - GimpContext *context = GIMP_CONTEXT (paint_options); - gint offset_x; - gint offset_y; + GimpMybrushCore *mybrush = GIMP_MYBRUSH_CORE (paint_core); + GimpMybrushOptions *mybrush_options = GIMP_MYBRUSH_OPTIONS (paint_options); + GimpContext *context = GIMP_CONTEXT (paint_options); + gint offset_x; + gint offset_y; g_return_if_fail (g_list_length (drawables) == 1); @@ -229,7 +233,8 @@ gimp_mybrush_core_paint (GimpPaintCore *paint_core, case GIMP_PAINT_STATE_MOTION: gimp_mybrush_core_motion (paint_core, drawables->data, paint_options, - sym, time); + sym, time, mybrush_options->view_zoom, + mybrush_options->view_rotation, 1.0f); break; case GIMP_PAINT_STATE_FINISH: @@ -249,10 +254,14 @@ gimp_mybrush_core_motion (GimpPaintCore *paint_core, GimpDrawable *drawable, GimpPaintOptions *paint_options, GimpSymmetry *sym, - guint32 time) + guint32 time, + gfloat view_zoom, + gfloat view_rotation, + gfloat barrel_rotation) { GimpMybrushCore *mybrush = GIMP_MYBRUSH_CORE (paint_core); MyPaintRectangle rect; + MyPaintRectangles rects = {1, &rect}; GimpCoords origin; GList *iter; gdouble dt = 0.0; @@ -288,14 +297,17 @@ gimp_mybrush_core_motion (GimpPaintCore *paint_core, MyPaintBrush *brush = iter->data; GimpCoords coords = *(gimp_symmetry_get_coords (sym, i)); - mypaint_brush_stroke_to (brush, - (MyPaintSurface *) mybrush->private->surface, - coords.x, - coords.y, - 0.0f, - coords.xtilt, - coords.ytilt, - 1.0f /* Pretend the cursor hasn't moved in a while */); + mypaint_brush_stroke_to_2 (brush, + (MyPaintSurface2 *) mybrush->private->surface, + coords.x, + coords.y, + 0.0f, + coords.xtilt, + coords.ytilt, + 1.0f, /* Pretend the cursor hasn't moved in a while */ + view_zoom, + view_rotation, + barrel_rotation); } dt = 0.015; @@ -356,29 +368,36 @@ gimp_mybrush_core_motion (GimpPaintCore *paint_core, gimp_symmetry_set_origin (sym, drawable, &origin); } - mypaint_brush_stroke_to (brush, - (MyPaintSurface *) mybrush->private->surface, - coords.x, - coords.y, - pressure, - coords.xtilt, - coords.ytilt, - dt); + mypaint_brush_stroke_to_2 (brush, + (MyPaintSurface2 *) mybrush->private->surface, + coords.x, + coords.y, + pressure, + coords.xtilt, + coords.ytilt, + dt, + view_zoom, + view_rotation, + barrel_rotation); } mybrush->private->last_time = time; - mypaint_surface_end_atomic ((MyPaintSurface *) mybrush->private->surface, - &rect); + mypaint_surface2_end_atomic ((MyPaintSurface2 *) mybrush->private->surface, + &rects); - if (rect.width > 0 && rect.height > 0) + if (rects.rectangles[0].width > 0 && rects.rectangles[0].height > 0) { - paint_core->x1 = MIN (paint_core->x1, rect.x); - paint_core->y1 = MIN (paint_core->y1, rect.y); - paint_core->x2 = MAX (paint_core->x2, rect.x + rect.width); - paint_core->y2 = MAX (paint_core->y2, rect.y + rect.height); - - gimp_drawable_update (drawable, rect.x, rect.y, rect.width, rect.height); + paint_core->x1 = MIN (paint_core->x1, rects.rectangles[0].x); + paint_core->y1 = MIN (paint_core->y1, rects.rectangles[0].y); + paint_core->x2 = MAX (paint_core->x2, + rects.rectangles[0].x + rects.rectangles[0].width); + paint_core->y2 = MAX (paint_core->y2, + rects.rectangles[0].y + rects.rectangles[0].height); + + gimp_drawable_update (drawable, rects.rectangles[0].x, + rects.rectangles[0].y, rects.rectangles[0].width, + rects.rectangles[0].height); } } @@ -413,7 +432,7 @@ gimp_mybrush_core_create_brushes (GimpMybrushCore *mybrush, for (i = 0; i < n_strokes; i++) { - MyPaintBrush *brush = mypaint_brush_new (); + MyPaintBrush *brush = mypaint_brush_new_with_buckets (64); const gchar *brush_data; mypaint_brush_from_defaults (brush); @@ -445,6 +464,18 @@ gimp_mybrush_core_create_brushes (GimpMybrushCore *mybrush, mypaint_brush_set_base_value (brush, MYPAINT_BRUSH_SETTING_HARDNESS, options->hardness); + mypaint_brush_set_base_value (brush, + MYPAINT_BRUSH_SETTING_PRESSURE_GAIN_LOG, + options->gain); + mypaint_brush_set_base_value (brush, + MYPAINT_BRUSH_SETTING_PAINT_MODE, + options->pigment); + mypaint_brush_set_base_value (brush, + MYPAINT_BRUSH_SETTING_POSTERIZE, + options->posterize); + mypaint_brush_set_base_value (brush, + MYPAINT_BRUSH_SETTING_POSTERIZE_NUM, + options->posterize_num); mypaint_brush_set_base_value (brush, MYPAINT_BRUSH_SETTING_ERASER, (options->eraser && diff --git a/app/paint/gimpmybrushoptions.c b/app/paint/gimpmybrushoptions.c index 4d515491a85..28efa87622b 100644 --- a/app/paint/gimpmybrushoptions.c +++ b/app/paint/gimpmybrushoptions.c @@ -38,9 +38,15 @@ enum { PROP_0, + PROP_VIEW_ZOOM, + PROP_VIEW_ROTATION, PROP_RADIUS, PROP_OPAQUE, PROP_HARDNESS, + PROP_GAIN, + PROP_PIGMENT, + PROP_POSTERIZE, + PROP_POSTERIZE_NUM, PROP_ERASER, PROP_NO_ERASING }; @@ -82,6 +88,20 @@ gimp_mybrush_options_class_init (GimpMybrushOptionsClass *klass) context_class->mybrush_changed = gimp_mybrush_options_mybrush_changed; + GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_RADIUS, + "viewzoom", + _("View Zoom"), + NULL, + 0.0001, G_MAXFLOAT, 1.0, + GIMP_PARAM_STATIC_STRINGS); + + GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_RADIUS, + "viewrotation", + _("View Rotation"), + NULL, + -360.0, 360.0, 0.0, + GIMP_PARAM_STATIC_STRINGS); + GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_RADIUS, "radius", _("Radius"), @@ -103,6 +123,31 @@ gimp_mybrush_options_class_init (GimpMybrushOptionsClass *klass) 0.0, 1.0, 1.0, GIMP_PARAM_STATIC_STRINGS); + GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_GAIN, + "gain", + _("Gain"), + _("Adjust strength of input pressue"), + -1.8, 1.8, 0.0, + GIMP_PARAM_STATIC_STRINGS); + + GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_PIGMENT, + "pigment", + NULL, NULL, + 0.0, 1.0, 0.0, + GIMP_PARAM_STATIC_STRINGS); + + GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_POSTERIZE, + "posterize", + NULL, NULL, + 0.0, 1.0, 0.0, + GIMP_PARAM_STATIC_STRINGS); + + GIMP_CONFIG_PROP_DOUBLE (object_class, PROP_POSTERIZE_NUM, + "posterizenum", + NULL, NULL, + 0.0, 1.28, 1.0, + GIMP_PARAM_STATIC_STRINGS); + GIMP_CONFIG_PROP_BOOLEAN (object_class, PROP_ERASER, "eraser", _("Erase with this brush"), @@ -141,14 +186,33 @@ gimp_mybrush_options_set_property (GObject *object, switch (property_id) { + case PROP_VIEW_ZOOM: + options->view_zoom = g_value_get_double (value) > 0.0f ? + g_value_get_double (value) : 0.0001f; + break; + case PROP_VIEW_ROTATION: + options->view_rotation = CLAMP (g_value_get_double (value), 0.0f, 360.0f); + break; case PROP_RADIUS: options->radius = g_value_get_double (value); break; + case PROP_OPAQUE: + options->opaque = g_value_get_double (value); + break; case PROP_HARDNESS: options->hardness = g_value_get_double (value); break; - case PROP_OPAQUE: - options->opaque = g_value_get_double (value); + case PROP_GAIN: + options->gain = g_value_get_double (value); + break; + case PROP_PIGMENT: + options->pigment = g_value_get_double (value); + break; + case PROP_POSTERIZE: + options->posterize = g_value_get_double (value); + break; + case PROP_POSTERIZE_NUM: + options->posterize_num = g_value_get_double (value); break; case PROP_ERASER: options->eraser = g_value_get_boolean (value); @@ -173,6 +237,12 @@ gimp_mybrush_options_get_property (GObject *object, switch (property_id) { + case PROP_VIEW_ZOOM: + g_value_set_double (value, options->view_zoom); + break; + case PROP_VIEW_ROTATION: + g_value_set_double (value, options->view_rotation); + break; case PROP_RADIUS: g_value_set_double (value, options->radius); break; @@ -182,6 +252,18 @@ gimp_mybrush_options_get_property (GObject *object, case PROP_HARDNESS: g_value_set_double (value, options->hardness); break; + case PROP_GAIN: + g_value_set_double (value, options->gain); + break; + case PROP_PIGMENT: + g_value_set_double (value, options->pigment); + break; + case PROP_POSTERIZE: + g_value_set_double (value, options->posterize); + break; + case PROP_POSTERIZE_NUM: + g_value_set_double (value, options->posterize_num); + break; case PROP_ERASER: g_value_set_boolean (value, options->eraser); break; @@ -201,10 +283,16 @@ gimp_mybrush_options_mybrush_changed (GimpContext *context, { if (brush) g_object_set (context, - "radius", gimp_mybrush_get_radius (brush), - "opaque", gimp_mybrush_get_opaque (brush), - "hardness", gimp_mybrush_get_hardness (brush), - "eraser", gimp_mybrush_get_is_eraser (brush), + "viewzoom", 1.0f, + "viewrotation", 0.0f, + "radius", gimp_mybrush_get_radius (brush), + "opaque", gimp_mybrush_get_opaque (brush), + "hardness", gimp_mybrush_get_hardness (brush), + "gain", gimp_mybrush_get_gain (brush), + "pigment", gimp_mybrush_get_pigment (brush), + "posterize", gimp_mybrush_get_posterize (brush), + "posterizenum", gimp_mybrush_get_posterize_num (brush), + "eraser", gimp_mybrush_get_is_eraser (brush), NULL); } diff --git a/app/paint/gimpmybrushoptions.h b/app/paint/gimpmybrushoptions.h index b1b5cd55a30..1e0e1f93145 100644 --- a/app/paint/gimpmybrushoptions.h +++ b/app/paint/gimpmybrushoptions.h @@ -36,9 +36,16 @@ struct _GimpMybrushOptions { GimpPaintOptions parent_instance; + gdouble view_zoom; + gdouble view_rotation; + gdouble radius; gdouble opaque; gdouble hardness; + gdouble gain; + gdouble pigment; + gdouble posterize; + gdouble posterize_num; gboolean eraser; gboolean no_erasing; }; diff --git a/app/paint/gimpmybrushsurface.c b/app/paint/gimpmybrushsurface.c index 195e8e99ad9..9f5b29317f8 100644 --- a/app/paint/gimpmybrushsurface.c +++ b/app/paint/gimpmybrushsurface.c @@ -34,7 +34,7 @@ struct _GimpMybrushSurface { - MyPaintSurface surface; + MyPaintSurface2 surface; GeglBuffer *buffer; GeglBuffer *paint_mask; gint paint_mask_x; @@ -232,17 +232,18 @@ calculate_dab_roi (float x, } static void -gimp_mypaint_surface_get_color (MyPaintSurface *base_surface, - float x, - float y, - float radius, - float *color_r, - float *color_g, - float *color_b, - float *color_a) +gimp_mypaint_surface_get_color_2 (MyPaintSurface2 *base_surface, + gfloat x, + gfloat y, + gfloat radius, + gfloat *color_r, + gfloat *color_g, + gfloat *color_b, + gfloat *color_a, + gfloat paint) { GimpMybrushSurface *surface = (GimpMybrushSurface *)base_surface; - GeglRectangle dabRect; + GeglRectangle dabRect; if (radius < 1.0f) radius = 1.0f; @@ -334,24 +335,41 @@ gimp_mypaint_surface_get_color (MyPaintSurface *base_surface, *color_a = CLAMP(sum_a, 0.0f, 1.0f); } } +} +static void +gimp_mypaint_surface_get_color_wrapper (MyPaintSurface *surface, + float x, + float y, + float radius, + float *color_r, + float *color_g, + float *color_b, + float *color_a) +{ + return gimp_mypaint_surface_get_color_2 ((MyPaintSurface2 *) surface, x, y, radius, + color_r, color_g, color_b, color_a, + -1.0); } -static int -gimp_mypaint_surface_draw_dab (MyPaintSurface *base_surface, - float x, - float y, - float radius, - float color_r, - float color_g, - float color_b, - float opaque, - float hardness, - float color_a, - float aspect_ratio, - float angle, - float lock_alpha, - float colorize) +static gint +gimp_mypaint_surface_draw_dab_2 (MyPaintSurface2 *base_surface, + gfloat x, + gfloat y, + gfloat radius, + gfloat color_r, + gfloat color_g, + gfloat color_b, + gfloat opaque, + gfloat hardness, + gfloat color_a, + gfloat aspect_ratio, + gfloat angle, + gfloat lock_alpha, + gfloat colorize, + gfloat posterize, + gfloat posterize_num, + gfloat paint) { GimpMybrushSurface *surface = (GimpMybrushSurface *)base_surface; GeglBufferIterator *iter; @@ -370,6 +388,10 @@ gimp_mypaint_surface_draw_dab (MyPaintSurface *base_surface, float segment2_slope; float r_aa_start; + posterize = CLAMP (posterize, 0.0f, 1.0f); + posterize_num = CLAMP (ROUND (posterize_num * 100.0), 1, 128); + paint = CLAMP (paint, 0.0f, 1.0f); + hardness = CLAMP (hardness, 0.0f, 1.0f); segment1_slope = -(1.0f / hardness - 1.0f); segment2_slope = -hardness / (1.0f - hardness); @@ -379,7 +401,7 @@ gimp_mypaint_surface_draw_dab (MyPaintSurface *base_surface, r_aa_start = MAX (r_aa_start, 0); r_aa_start = (r_aa_start * r_aa_start) / aspect_ratio; - normal_mode = opaque * (1.0f - colorize); + normal_mode = opaque * (1.0f - colorize) * (1.0f - posterize); colorize = opaque * colorize; /* FIXME: This should use the real matrix values to trim aspect_ratio dabs */ @@ -481,6 +503,26 @@ gimp_mypaint_surface_draw_dab (MyPaintSurface *base_surface, } } + if (posterize > 0.0f && base_alpha > 0.0f) + { + alpha = base_alpha * posterize; + a = alpha + dst_alpha - alpha * dst_alpha; + if (a > 0.0f) + { + gfloat post_pixel[3]; + gfloat src_term = alpha / a; + gfloat dst_term = 1.0f - src_term; + + post_pixel[0] = ROUND (r * posterize_num) / posterize_num; + post_pixel[1] = ROUND (g * posterize_num) / posterize_num; + post_pixel[2] = ROUND (b * posterize_num) / posterize_num; + + r = post_pixel[0] * src_term + r * dst_term; + g = post_pixel[1] * src_term + g * dst_term; + b = post_pixel[2] * src_term + b * dst_term; + } + } + if (surface->options->no_erasing) a = MAX (a, pixel[ALPHA]); @@ -513,6 +555,33 @@ gimp_mypaint_surface_draw_dab (MyPaintSurface *base_surface, return 1; } +static int +gimp_mypaint_surface_draw_dab_wrapper (MyPaintSurface *surface, + float x, + float y, + float radius, + float color_r, + float color_g, + float color_b, + float opaque, + float hardness, + float color_a, + float aspect_ratio, + float angle, + float lock_alpha, + float colorize) +{ + const gfloat posterize = 0.0; + const gfloat posterize_num = 1.0; + const gfloat pigment = 0.0; + + return gimp_mypaint_surface_draw_dab_2 ((MyPaintSurface2 *) surface, x, y, radius, + color_r, color_g, color_b, opaque, hardness, + color_a, aspect_ratio, angle, lock_alpha, + colorize, posterize, posterize_num, + pigment); +} + static void gimp_mypaint_surface_begin_atomic (MyPaintSurface *base_surface) { @@ -520,22 +589,39 @@ gimp_mypaint_surface_begin_atomic (MyPaintSurface *base_surface) } static void -gimp_mypaint_surface_end_atomic (MyPaintSurface *base_surface, - MyPaintRectangle *roi) +gimp_mypaint_surface_end_atomic_2 (MyPaintSurface2 *base_surface, + MyPaintRectangles *rois) { GimpMybrushSurface *surface = (GimpMybrushSurface *)base_surface; - roi->x = surface->dirty.x; - roi->y = surface->dirty.y; - roi->width = surface->dirty.width; - roi->height = surface->dirty.height; - surface->dirty = *GEGL_RECTANGLE (0, 0, 0, 0); + if (rois) + { + const gint roi_rects = rois->num_rectangles; + + for (gint i = 0; i < roi_rects; i++) + { + rois->rectangles[i].x = surface->dirty.x; + rois->rectangles[i].y = surface->dirty.y; + rois->rectangles[i].width = surface->dirty.width; + rois->rectangles[i].height = surface->dirty.height; + surface->dirty = *GEGL_RECTANGLE (0, 0, 0, 0); + } + } +} + +static void +gimp_mypaint_surface_end_atomic_wrapper (MyPaintSurface *surface, + MyPaintRectangle *roi) +{ + MyPaintRectangles rois = {1, roi}; + + gimp_mypaint_surface_end_atomic_2 ((MyPaintSurface2 *) surface, &rois); } static void gimp_mypaint_surface_destroy (MyPaintSurface *base_surface) { - GimpMybrushSurface *surface = (GimpMybrushSurface *)base_surface; + GimpMybrushSurface *surface = (GimpMybrushSurface *) base_surface; g_clear_object (&surface->buffer); g_clear_object (&surface->paint_mask); @@ -551,26 +637,34 @@ gimp_mypaint_surface_new (GeglBuffer *buffer, GimpMybrushOptions *options) { GimpMybrushSurface *surface = g_malloc0 (sizeof (GimpMybrushSurface)); + MyPaintSurface2 *s; + + mypaint_surface_init (&surface->surface.parent); + s = &surface->surface; + + s->get_color_pigment = gimp_mypaint_surface_get_color_2; + s->draw_dab_pigment = gimp_mypaint_surface_draw_dab_2; + s->parent.begin_atomic = gimp_mypaint_surface_begin_atomic; + s->end_atomic_multi = gimp_mypaint_surface_end_atomic_2; + + s->parent.draw_dab = gimp_mypaint_surface_draw_dab_wrapper; + s->parent.get_color = gimp_mypaint_surface_get_color_wrapper; + s->parent.end_atomic = gimp_mypaint_surface_end_atomic_wrapper; - mypaint_surface_init ((MyPaintSurface *)surface); + s->parent.destroy = gimp_mypaint_surface_destroy; - surface->surface.get_color = gimp_mypaint_surface_get_color; - surface->surface.draw_dab = gimp_mypaint_surface_draw_dab; - surface->surface.begin_atomic = gimp_mypaint_surface_begin_atomic; - surface->surface.end_atomic = gimp_mypaint_surface_end_atomic; - surface->surface.destroy = gimp_mypaint_surface_destroy; - surface->component_mask = component_mask; - surface->options = options; - surface->buffer = g_object_ref (buffer); + surface->component_mask = component_mask; + surface->options = options; + surface->buffer = g_object_ref (buffer); if (paint_mask) - surface->paint_mask = g_object_ref (paint_mask); + surface->paint_mask = g_object_ref (paint_mask); - surface->paint_mask_x = paint_mask_x; - surface->paint_mask_y = paint_mask_y; - surface->dirty = *GEGL_RECTANGLE (0, 0, 0, 0); + surface->paint_mask_x = paint_mask_x; + surface->paint_mask_y = paint_mask_y; + surface->dirty = *GEGL_RECTANGLE (0, 0, 0, 0); - surface->off_x = 0; - surface->off_y = 0; + surface->off_x = 0; + surface->off_y = 0; return surface; } diff --git a/app/tools/gimpmybrushoptions-gui.c b/app/tools/gimpmybrushoptions-gui.c index 65315386760..325618558a9 100644 --- a/app/tools/gimpmybrushoptions-gui.c +++ b/app/tools/gimpmybrushoptions-gui.c @@ -80,6 +80,11 @@ gimp_mybrush_options_gui (GimpToolOptions *tool_options) 0.1, 1.0, 2); gtk_box_pack_start (GTK_BOX (vbox2), scale, FALSE, FALSE, 0); + /* pressure gain */ + scale = gimp_prop_spin_scale_new (config, "gain", + 0.1, 0.0, 2); + gtk_box_pack_start (GTK_BOX (vbox2), scale, FALSE, FALSE, 0); + /* erase mode */ scale = gimp_prop_check_button_new (config, "eraser", NULL); gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0); diff --git a/app/tools/gimpmybrushtool.c b/app/tools/gimpmybrushtool.c index 29182c1684b..ef1ae3da17e 100644 --- a/app/tools/gimpmybrushtool.c +++ b/app/tools/gimpmybrushtool.c @@ -46,7 +46,10 @@ G_DEFINE_TYPE (GimpMybrushTool, gimp_mybrush_tool, GIMP_TYPE_PAINT_TOOL) #define parent_class gimp_mybrush_tool_parent_class - +static void gimp_mybrush_tool_cursor_update (GimpTool *tool, + const GimpCoords *coords, + GdkModifierType state, + GimpDisplay *display); static void gimp_mybrush_tool_options_notify (GimpTool *tool, GimpToolOptions *options, const GParamSpec *pspec); @@ -86,6 +89,7 @@ gimp_mybrush_tool_class_init (GimpMybrushToolClass *klass) GimpToolClass *tool_class = GIMP_TOOL_CLASS (klass); GimpPaintToolClass *paint_tool_class = GIMP_PAINT_TOOL_CLASS (klass); + tool_class->cursor_update = gimp_mybrush_tool_cursor_update; tool_class->options_notify = gimp_mybrush_tool_options_notify; paint_tool_class->get_outline = gimp_mybrush_tool_get_outline; @@ -108,6 +112,23 @@ gimp_mybrush_tool_init (GimpMybrushTool *mybrush_tool) GIMP_COLOR_PICK_TARGET_FOREGROUND); } +static void gimp_mybrush_tool_cursor_update (GimpTool *tool, + const GimpCoords *coords, + GdkModifierType state, + GimpDisplay *display) +{ + GimpDisplayShell *shell; + GimpMybrushOptions *options = GIMP_MYBRUSH_TOOL_GET_OPTIONS (tool); + + g_return_if_fail (GIMP_IS_DISPLAY (display)); + + shell = gimp_display_get_shell (display); + + options->view_zoom = (gimp_zoom_model_get_factor (shell->zoom) >= 0.0f) ? + gimp_zoom_model_get_factor (shell->zoom) : 0.0001f; + options->view_rotation = shell->rotate_angle; +} + static void gimp_mybrush_tool_options_notify (GimpTool *tool, GimpToolOptions *options, -- GitLab From 93c5319c36ad518ee476e07231a356280be34095 Mon Sep 17 00:00:00 2001 From: Alx Sa Date: Sat, 5 Jul 2025 03:28:22 +0000 Subject: [PATCH 2/3] build: Changes to MyPaint-Brushes2 Making this a separate commit to make it easier for Bruno to look over --- app/config/gimpcoreconfig.c | 2 +- build/linux/appimage/3_dist-gimp-goappimage.sh | 2 +- build/linux/flatpak/org.gimp.GIMP-nightly.json | 8 ++------ build/windows/1_build-deps-msys2.ps1 | 3 ++- build/windows/2_bundle-gimp-uni_base.py | 11 +++++++---- build/windows/all-deps-uni.txt | 1 - meson.build | 7 +++++-- 7 files changed, 18 insertions(+), 16 deletions(-) diff --git a/app/config/gimpcoreconfig.c b/app/config/gimpcoreconfig.c index 35971f995b1..8d6b1b7fc6d 100644 --- a/app/config/gimpcoreconfig.c +++ b/app/config/gimpcoreconfig.c @@ -311,7 +311,7 @@ gimp_core_config_class_init (GimpCoreConfigClass *klass) #ifdef ENABLE_RELOCATABLE_RESOURCES mypaint_brushes = g_build_filename ("${gimp_installation_dir}", "share", "mypaint-data", - "1.0", "brushes", NULL); + "2.0", "brushes", NULL); #else mypaint_brushes = g_strdup (MYPAINT_BRUSHES_DIR); #endif diff --git a/build/linux/appimage/3_dist-gimp-goappimage.sh b/build/linux/appimage/3_dist-gimp-goappimage.sh index ce5b5b9f469..0d89b3554ed 100644 --- a/build/linux/appimage/3_dist-gimp-goappimage.sh +++ b/build/linux/appimage/3_dist-gimp-goappimage.sh @@ -324,7 +324,7 @@ bund_usr "$GIMP_PREFIX" "etc/gimp" ## Other features and plug-ins ### mypaint brushes -bund_usr "$UNIX_PREFIX" "share/mypaint-data/1.0" +bund_usr "$UNIX_PREFIX" "share/mypaint-data/2.0" ### Needed for 'th' word breaking in Text tool etc bund_usr "$UNIX_PREFIX" "share/libthai" conf_app LIBTHAI_DICTDIR "share/libthai" diff --git a/build/linux/flatpak/org.gimp.GIMP-nightly.json b/build/linux/flatpak/org.gimp.GIMP-nightly.json index c64c639f484..3e03d6b9f1a 100644 --- a/build/linux/flatpak/org.gimp.GIMP-nightly.json +++ b/build/linux/flatpak/org.gimp.GIMP-nightly.json @@ -482,16 +482,12 @@ "sources": [ { "type": "archive", - "url": "https://github.com/mypaint/mypaint-brushes/releases/download/v1.3.1/mypaint-brushes-1.3.1.tar.xz", - "sha256": "fef66ffc241b7c5cd29e9c518e933c739618cb51c4ed4d745bf648a1afc3fe70", + "url": "https://github.com/mypaint/mypaint-brushes/releases/download/v2.0.2/mypaint-brushes-2.0.2.tar.xz", + "sha256": "7984a74edef94571d872d0629b224abaa956a36f632f5c5516b33d22e49eb566", "x-checker-data": { "type": "anitya", "project-id": 17096, "stable-only": true, - "//": "https://github.com/mypaint/libmypaint/issues/101", - "versions": { - "<": "2.0.0" - }, "url-template": "https://github.com/mypaint/mypaint-brushes/releases/download/v$version/mypaint-brushes-$version.tar.xz" } } diff --git a/build/windows/1_build-deps-msys2.ps1 b/build/windows/1_build-deps-msys2.ps1 index 5be1037e2be..3fb69d601c5 100644 --- a/build/windows/1_build-deps-msys2.ps1 +++ b/build/windows/1_build-deps-msys2.ps1 @@ -42,7 +42,8 @@ if ("$PSCommandPath" -like "*1_build-deps-msys2.ps1*" -or "$CI_JOB_NAME" -like " { & $MSYS_ROOT\usr\bin\pacman --noconfirm -Suy } -& $MSYS_ROOT\usr\bin\pacman --noconfirm -S --needed $(if ($MSYSTEM_PREFIX -ne 'mingw32') { "$(if ($MSYSTEM_PREFIX -eq 'clangarm64') { 'mingw-w64-clang-aarch64' } else { 'mingw-w64-clang-x86_64' })-perl" }) (Get-Content build/windows/all-deps-uni.txt | Where-Object { $_.Trim() -ne '' }).Replace('${MINGW_PACKAGE_PREFIX}',$(if ($MINGW_PACKAGE_PREFIX) { "$MINGW_PACKAGE_PREFIX" } elseif ($MSYSTEM_PREFIX -eq 'clangarm64') { 'mingw-w64-clang-aarch64' } else { 'mingw-w64-clang-x86_64' })).Replace(' \','') +# TODO: When we drop 32-bit support, move an unconditional mypaint-brushes2 dependency back to all-deps-uni.txt +& $MSYS_ROOT\usr\bin\pacman --noconfirm -S --needed $(if ($MSYSTEM_PREFIX -ne 'mingw32') { "$(if ($MSYSTEM_PREFIX -eq 'clangarm64') { 'mingw-w64-clang-aarch64' } else { 'mingw-w64-clang-x86_64' })-perl`n$(if ($MSYSTEM_PREFIX -eq 'clangarm64') { 'mingw-w64-clang-aarch64' } else { 'mingw-w64-clang-x86_64' })-mypaint-brushes2" } else { 'mingw-w64-i686-mypaint-brushes' }) (Get-Content build/windows/all-deps-uni.txt).Replace('${MINGW_PACKAGE_PREFIX}',$(if ($MINGW_PACKAGE_PREFIX) { "$MINGW_PACKAGE_PREFIX" } elseif ($MSYSTEM_PREFIX -eq 'clangarm64') { 'mingw-w64-clang-aarch64' } else { 'mingw-w64-clang-x86_64' })).Replace(' \','') Write-Output "$([char]27)[0Ksection_end:$(Get-Date -UFormat %s -Millisecond 0):deps_install$([char]13)$([char]27)[0K" diff --git a/build/windows/2_bundle-gimp-uni_base.py b/build/windows/2_bundle-gimp-uni_base.py index 45b4fed5a45..c486d669395 100644 --- a/build/windows/2_bundle-gimp-uni_base.py +++ b/build/windows/2_bundle-gimp-uni_base.py @@ -14,7 +14,7 @@ if not os.getenv("MESON_BUILD_ROOT"): # Bundle deps and GIMP files -GIMP_SOURCE = Path(os.getenv("MESON_SOURCE_ROOT")).as_posix() +GIMP_SOURCE = Path(os.getenv("MESON_SOURCE_ROOT")).as_posix() ## System prefix: it is MSYSTEM_PREFIX with open("meson-logs/meson-log.txt") as f: @@ -144,7 +144,10 @@ for lang in lang_array: # For language list in text tool options bundle(MSYSTEM_PREFIX, f"share/locale/{lang}/LC_MESSAGES/iso_639_3.mo") ### mypaint brushes -bundle(MSYSTEM_PREFIX, "share/mypaint-data") +if "32" not in MSYSTEM_PREFIX: + bundle(MSYSTEM_PREFIX, "share/mypaint-data/2.0") +else: + bundle(MSYSTEM_PREFIX, "share/mypaint-data/1.0") ### Needed for full CJK and Cyrillic support in file-pdf bundle(MSYSTEM_PREFIX, "share/poppler") @@ -164,7 +167,7 @@ if (os.getenv("GIMP_UNSTABLE") or not os.getenv("GIMP_RELEASE")) and "32" not in #### See: https://gitlab.gnome.org/GNOME/gimp/-/issues/12119 bundle(MSYSTEM_PREFIX, "bin/libgvplugin_dot*.dll") bundle(MSYSTEM_PREFIX, "bin/libgvplugin_pango*.dll") - bundle(MSYSTEM_PREFIX, "bin/config6") + bundle(MSYSTEM_PREFIX, "bin/config6") ### Needed to not pollute output. See: https://gitlab.gnome.org/GNOME/gimp/-/issues/8877 bundle(MSYSTEM_PREFIX, "bin/gdbus.exe") ### Needed for hyperlink support etc... See: https://gitlab.gnome.org/GNOME/gimp/-/issues/12288 @@ -173,7 +176,7 @@ bundle(MSYSTEM_PREFIX, "bin/gspawn*-console.exe") if not os.getenv("GIMP_UNSTABLE") and os.getenv("GIMP_RELEASE"): #...when running from `gimp*.exe` bundle(MSYSTEM_PREFIX, "bin/gspawn*-helper.exe") - + ## Binaries for GObject Introspection support. See: https://gitlab.gnome.org/GNOME/gimp/-/issues/13170 bundle(GIMP_PREFIX, "lib/girepository-*/*.typelib") bundle(MSYSTEM_PREFIX, "bin/libgirepository-*.dll") diff --git a/build/windows/all-deps-uni.txt b/build/windows/all-deps-uni.txt index 85e65202d79..0bb34567d04 100644 --- a/build/windows/all-deps-uni.txt +++ b/build/windows/all-deps-uni.txt @@ -37,7 +37,6 @@ ${MINGW_PACKAGE_PREFIX}-libtiff \ ${MINGW_PACKAGE_PREFIX}-libwebp \ ${MINGW_PACKAGE_PREFIX}-libwmf \ ${MINGW_PACKAGE_PREFIX}-maxflow \ -${MINGW_PACKAGE_PREFIX}-mypaint-brushes \ ${MINGW_PACKAGE_PREFIX}-openexr \ ${MINGW_PACKAGE_PREFIX}-openjpeg2 \ ${MINGW_PACKAGE_PREFIX}-pango \ diff --git a/meson.build b/meson.build index 0f340186a53..39439c31730 100644 --- a/meson.build +++ b/meson.build @@ -444,7 +444,10 @@ lcms_minver = '2.8' lcms = dependency('lcms2', version: '>='+lcms_minver) libmypaint_minver = '1.3.0' libmypaint = dependency('libmypaint', version: '>='+libmypaint_minver) -mypaint_brushes = dependency('mypaint-brushes-1.0',version: '>='+libmypaint_minver) +mypaint_brushes = dependency('mypaint-brushes-2.0',version: '>='+libmypaint_minver, required: false) +if not mypaint_brushes.found() + mypaint_brushes = dependency('mypaint-brushes-1.0',version: '>='+libmypaint_minver) +endif if not libmypaint.version().version_compare('>=1.4.0') libmypaint_warning=''' @@ -458,7 +461,7 @@ endif if relocatable_bundle mypaint_brushes_dir = '${gimp_installation_dir}'\ - /'share'/'mypaint-data'/'1.0'/'brushes' + /'share'/'mypaint-data'/'2.0'/'brushes' else mypaint_brushes_dir = mypaint_brushes.get_variable(pkgconfig: 'brushesdir') endif -- GitLab From 2eec67423a7eaf0c19657defc75ae3fa45675670 Mon Sep 17 00:00:00 2001 From: Alx Sa Date: Tue, 8 Jul 2025 03:23:22 +0000 Subject: [PATCH 3/3] meson.build: Improve dependency logic We now use the platform and host_cpu_family values to determine if we load 2.0 vs 1.0, reducing the risk of someone with 1.0 brushes missing out when they could install 2.0 brushes instead. --- meson.build | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/meson.build b/meson.build index 39439c31730..3b5506b95c1 100644 --- a/meson.build +++ b/meson.build @@ -444,10 +444,11 @@ lcms_minver = '2.8' lcms = dependency('lcms2', version: '>='+lcms_minver) libmypaint_minver = '1.3.0' libmypaint = dependency('libmypaint', version: '>='+libmypaint_minver) -mypaint_brushes = dependency('mypaint-brushes-2.0',version: '>='+libmypaint_minver, required: false) -if not mypaint_brushes.found() - mypaint_brushes = dependency('mypaint-brushes-1.0',version: '>='+libmypaint_minver) +mypaint_brushes_n = 'mypaint-brushes-2.0' +if platform_windows and host_cpu_family == 'x86' + mypaint_brushes_n = 'mypaint-brushes-1.0' endif +mypaint_brushes = dependency(mypaint_brushes_n,version: '>='+libmypaint_minver) if not libmypaint.version().version_compare('>=1.4.0') libmypaint_warning=''' -- GitLab