app: Fix MyPaint brush rendering, implement MyPaintSurface in a native format
This commit is contained in:
@ -56,6 +56,8 @@ libapppaint_a_sources = \
|
||||
gimpmybrush.h \
|
||||
gimpmybrushoptions.c \
|
||||
gimpmybrushoptions.h \
|
||||
gimpmybrushsurface.c \
|
||||
gimpmybrushsurface.h \
|
||||
gimppaintcore.c \
|
||||
gimppaintcore.h \
|
||||
gimppaintcore-loops.c \
|
||||
|
@ -26,8 +26,10 @@
|
||||
#include <gegl.h>
|
||||
|
||||
#include <mypaint-brush.h>
|
||||
#if 0
|
||||
#include <mypaint-tiled-surface.h>
|
||||
#include <mypaint-gegl-surface.h>
|
||||
#endif
|
||||
|
||||
#include "libgimpmath/gimpmath.h"
|
||||
#include "libgimpcolor/gimpcolor.h"
|
||||
@ -45,6 +47,7 @@
|
||||
#include "core/gimpimage-undo.h"
|
||||
#include "core/gimptempbuf.h"
|
||||
|
||||
#include "gimpmybrushsurface.h"
|
||||
#include "gimpmybrushoptions.h"
|
||||
#include "gimpmybrush.h"
|
||||
|
||||
@ -53,8 +56,13 @@
|
||||
|
||||
struct _GimpMybrushPrivate
|
||||
{
|
||||
#if 0
|
||||
MyPaintGeglTiledSurface *surface;
|
||||
#else
|
||||
GimpMybrushSurface *surface;
|
||||
#endif
|
||||
MyPaintBrush *brush;
|
||||
gint64 lastTime;
|
||||
};
|
||||
|
||||
|
||||
@ -119,7 +127,13 @@ gimp_mybrush_paint (GimpPaintCore *paint_core,
|
||||
{
|
||||
GimpMybrush *mybrush = GIMP_MYBRUSH (paint_core);
|
||||
GimpMybrushOptions *options = GIMP_MYBRUSH_OPTIONS (paint_options);
|
||||
const gchar *brush_data;
|
||||
#if 0
|
||||
GeglBuffer *buffer;
|
||||
GimpComponentMask active_mask;
|
||||
#endif
|
||||
GimpRGB fg;
|
||||
GimpHSV hsv;
|
||||
|
||||
switch (paint_state)
|
||||
{
|
||||
@ -132,6 +146,7 @@ gimp_mybrush_paint (GimpPaintCore *paint_core,
|
||||
gimp_palettes_add_color_history (context->gimp,
|
||||
&foreground);
|
||||
|
||||
#if 0
|
||||
mybrush->private->surface = mypaint_gegl_tiled_surface_new ();
|
||||
|
||||
buffer = mypaint_gegl_tiled_surface_get_buffer (mybrush->private->surface);
|
||||
@ -144,26 +159,51 @@ gimp_mybrush_paint (GimpPaintCore *paint_core,
|
||||
buffer, NULL);
|
||||
mypaint_gegl_tiled_surface_set_buffer (mybrush->private->surface, buffer);
|
||||
g_object_unref (buffer);
|
||||
#else
|
||||
mybrush->private->surface = gimp_mypaint_surface_new (gimp_drawable_get_buffer (drawable),
|
||||
gimp_drawable_get_active_mask (drawable));
|
||||
#endif
|
||||
|
||||
mybrush->private->brush = mypaint_brush_new ();
|
||||
mypaint_brush_from_defaults (mybrush->private->brush);
|
||||
brush_data = gimp_mybrush_options_get_brush_data (options);
|
||||
if (brush_data)
|
||||
mypaint_brush_from_string (mybrush->private->brush, brush_data);
|
||||
|
||||
if (options->mybrush)
|
||||
{
|
||||
gchar *string;
|
||||
gsize length;
|
||||
#if 0
|
||||
active_mask = gimp_drawable_get_active_mask (drawable);
|
||||
|
||||
if (g_file_get_contents (options->mybrush,
|
||||
&string, &length, NULL))
|
||||
{
|
||||
if (! mypaint_brush_from_string (mybrush->private->brush, string))
|
||||
g_printerr ("Failed to deserialize MyPaint brush\n");
|
||||
mypaint_brush_set_base_value (mybrush->private->brush,
|
||||
MYPAINT_BRUSH_SETTING_LOCK_ALPHA,
|
||||
(active_mask & GIMP_COMPONENT_MASK_ALPHA) ?
|
||||
FALSE : TRUE);
|
||||
#endif
|
||||
|
||||
g_free (string);
|
||||
}
|
||||
}
|
||||
gimp_context_get_foreground (context, &fg);
|
||||
gimp_rgb_to_hsv (&fg, &hsv);
|
||||
|
||||
mypaint_brush_set_base_value (mybrush->private->brush,
|
||||
MYPAINT_BRUSH_SETTING_COLOR_H,
|
||||
hsv.h);
|
||||
mypaint_brush_set_base_value (mybrush->private->brush,
|
||||
MYPAINT_BRUSH_SETTING_COLOR_S,
|
||||
hsv.s);
|
||||
mypaint_brush_set_base_value (mybrush->private->brush,
|
||||
MYPAINT_BRUSH_SETTING_COLOR_V,
|
||||
hsv.v);
|
||||
|
||||
mypaint_brush_set_base_value (mybrush->private->brush,
|
||||
MYPAINT_BRUSH_SETTING_RADIUS_LOGARITHMIC,
|
||||
options->radius);
|
||||
mypaint_brush_set_base_value (mybrush->private->brush,
|
||||
MYPAINT_BRUSH_SETTING_OPAQUE,
|
||||
options->opaque * gimp_context_get_opacity (context));
|
||||
mypaint_brush_set_base_value (mybrush->private->brush,
|
||||
MYPAINT_BRUSH_SETTING_HARDNESS,
|
||||
options->hardness);
|
||||
|
||||
mypaint_brush_new_stroke (mybrush->private->brush);
|
||||
mybrush->private->lastTime = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
@ -189,45 +229,26 @@ gimp_mybrush_motion (GimpPaintCore *paint_core,
|
||||
guint32 time)
|
||||
{
|
||||
GimpMybrush *mybrush = GIMP_MYBRUSH (paint_core);
|
||||
GimpMybrushOptions *options = GIMP_MYBRUSH_OPTIONS (paint_options);
|
||||
GimpContext *context = GIMP_CONTEXT (paint_options);
|
||||
GimpComponentMask active_mask;
|
||||
GimpRGB fg;
|
||||
GimpHSV hsv;
|
||||
MyPaintRectangle rect;
|
||||
|
||||
active_mask = gimp_drawable_get_active_mask (drawable);
|
||||
|
||||
mypaint_brush_set_base_value (mybrush->private->brush,
|
||||
MYPAINT_BRUSH_SETTING_LOCK_ALPHA,
|
||||
(active_mask & GIMP_COMPONENT_MASK_ALPHA) ?
|
||||
FALSE : TRUE);
|
||||
|
||||
gimp_context_get_foreground (context, &fg);
|
||||
gimp_rgb_to_hsv (&fg, &hsv);
|
||||
|
||||
mypaint_brush_set_base_value (mybrush->private->brush,
|
||||
MYPAINT_BRUSH_SETTING_COLOR_H,
|
||||
hsv.h);
|
||||
mypaint_brush_set_base_value (mybrush->private->brush,
|
||||
MYPAINT_BRUSH_SETTING_COLOR_S,
|
||||
hsv.s);
|
||||
mypaint_brush_set_base_value (mybrush->private->brush,
|
||||
MYPAINT_BRUSH_SETTING_COLOR_V,
|
||||
hsv.v);
|
||||
|
||||
mypaint_brush_set_base_value (mybrush->private->brush,
|
||||
MYPAINT_BRUSH_SETTING_OPAQUE,
|
||||
gimp_context_get_opacity (context));
|
||||
mypaint_brush_set_base_value (mybrush->private->brush,
|
||||
MYPAINT_BRUSH_SETTING_RADIUS_LOGARITHMIC,
|
||||
options->radius);
|
||||
mypaint_brush_set_base_value (mybrush->private->brush,
|
||||
MYPAINT_BRUSH_SETTING_HARDNESS,
|
||||
options->hardness);
|
||||
|
||||
mypaint_surface_begin_atomic ((MyPaintSurface *) mybrush->private->surface);
|
||||
|
||||
|
||||
if (mybrush->private->lastTime == 0)
|
||||
{
|
||||
/* First motion, so we need a zero pressure event to start the stroke */
|
||||
mybrush->private->lastTime = (gint64)time - 15;
|
||||
|
||||
mypaint_brush_stroke_to (mybrush->private->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 (mybrush->private->brush,
|
||||
(MyPaintSurface *) mybrush->private->surface,
|
||||
coords->x,
|
||||
@ -235,16 +256,15 @@ gimp_mybrush_motion (GimpPaintCore *paint_core,
|
||||
coords->pressure,
|
||||
coords->xtilt,
|
||||
coords->ytilt,
|
||||
1);
|
||||
(time - mybrush->private->lastTime) * 0.001f);
|
||||
mybrush->private->lastTime = time;
|
||||
|
||||
mypaint_surface_end_atomic ((MyPaintSurface *) mybrush->private->surface,
|
||||
&rect);
|
||||
|
||||
g_printerr ("painted rect: %d %d %d %d\n",
|
||||
rect.x, rect.y, rect.width, rect.height);
|
||||
|
||||
if (rect.width > 0 && rect.height > 0)
|
||||
{
|
||||
#if 0
|
||||
GeglBuffer *src;
|
||||
|
||||
src = mypaint_gegl_tiled_surface_get_buffer (mybrush->private->surface);
|
||||
@ -254,7 +274,7 @@ gimp_mybrush_motion (GimpPaintCore *paint_core,
|
||||
GEGL_ABYSS_NONE,
|
||||
gimp_drawable_get_buffer (drawable),
|
||||
NULL);
|
||||
|
||||
#endif
|
||||
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);
|
||||
|
@ -34,15 +34,26 @@
|
||||
|
||||
#include "gimp-intl.h"
|
||||
|
||||
#include <mypaint-brush.h>
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_RADIUS,
|
||||
PROP_OPAQUE,
|
||||
PROP_HARDNESS,
|
||||
PROP_MYBRUSH
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
gdouble radius;
|
||||
gdouble opaque;
|
||||
gdouble hardness;
|
||||
gchar *brush_json;
|
||||
} OptionsState;
|
||||
|
||||
static GHashTable *loaded_myb;
|
||||
|
||||
static void gimp_mybrush_options_set_property (GObject *object,
|
||||
guint property_id,
|
||||
@ -52,6 +63,12 @@ static void gimp_mybrush_options_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec);
|
||||
static void options_state_free (gpointer options)
|
||||
{
|
||||
OptionsState *state = options;
|
||||
g_free (state->brush_json);
|
||||
g_free (state);
|
||||
}
|
||||
|
||||
|
||||
G_DEFINE_TYPE (GimpMybrushOptions, gimp_mybrush_options, GIMP_TYPE_PAINT_OPTIONS)
|
||||
@ -69,6 +86,10 @@ gimp_mybrush_options_class_init (GimpMybrushOptionsClass *klass)
|
||||
"radius", _("Radius"),
|
||||
-2.0, 6.0, 1.0,
|
||||
GIMP_PARAM_STATIC_STRINGS);
|
||||
GIMP_CONFIG_INSTALL_PROP_DOUBLE (object_class, PROP_OPAQUE,
|
||||
"opaque", _("Base Opacity"),
|
||||
0.0, 2.0, 1.0,
|
||||
GIMP_PARAM_STATIC_STRINGS);
|
||||
GIMP_CONFIG_INSTALL_PROP_DOUBLE (object_class, PROP_HARDNESS,
|
||||
"hardness", NULL,
|
||||
0.0, 1.0, 1.0,
|
||||
@ -77,6 +98,50 @@ gimp_mybrush_options_class_init (GimpMybrushOptionsClass *klass)
|
||||
"mybrush", NULL,
|
||||
NULL,
|
||||
GIMP_PARAM_STATIC_STRINGS);
|
||||
|
||||
loaded_myb = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, options_state_free);
|
||||
}
|
||||
|
||||
static void
|
||||
gimp_mybrush_options_load_path (GObject *object,
|
||||
gchar *path)
|
||||
{
|
||||
GimpMybrushOptions *options = GIMP_MYBRUSH_OPTIONS (object);
|
||||
|
||||
OptionsState *state = g_hash_table_lookup (loaded_myb, path);
|
||||
if (!state)
|
||||
{
|
||||
gchar *brush_json = NULL;
|
||||
MyPaintBrush *brush = mypaint_brush_new ();
|
||||
|
||||
state = g_new0 (OptionsState, 1);
|
||||
mypaint_brush_from_defaults (brush);
|
||||
|
||||
if (g_file_get_contents (path, &brush_json, NULL, NULL))
|
||||
{
|
||||
if (! mypaint_brush_from_string (brush, brush_json))
|
||||
{
|
||||
g_printerr ("Failed to deserialize MyPaint brush\n");
|
||||
g_free (brush_json);
|
||||
brush_json = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
state->brush_json = brush_json;
|
||||
state->radius = mypaint_brush_get_base_value (brush, MYPAINT_BRUSH_SETTING_RADIUS_LOGARITHMIC);
|
||||
state->opaque = mypaint_brush_get_base_value (brush, MYPAINT_BRUSH_SETTING_OPAQUE);
|
||||
state->hardness = mypaint_brush_get_base_value (brush, MYPAINT_BRUSH_SETTING_HARDNESS);
|
||||
|
||||
g_hash_table_insert (loaded_myb, g_strdup(path), state);
|
||||
}
|
||||
|
||||
options->radius = state->radius;
|
||||
options->opaque = state->opaque;
|
||||
options->hardness = state->hardness;
|
||||
|
||||
g_object_notify (object, "radius");
|
||||
g_object_notify (object, "opaque");
|
||||
g_object_notify (object, "hardness");
|
||||
}
|
||||
|
||||
static void
|
||||
@ -100,9 +165,14 @@ gimp_mybrush_options_set_property (GObject *object,
|
||||
case PROP_HARDNESS:
|
||||
options->hardness = g_value_get_double (value);
|
||||
break;
|
||||
case PROP_OPAQUE:
|
||||
options->opaque = g_value_get_double (value);
|
||||
break;
|
||||
case PROP_MYBRUSH:
|
||||
g_free (options->mybrush);
|
||||
options->mybrush = g_value_dup_string (value);
|
||||
if (options->mybrush)
|
||||
gimp_mybrush_options_load_path (object, options->mybrush);
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -124,6 +194,9 @@ gimp_mybrush_options_get_property (GObject *object,
|
||||
case PROP_RADIUS:
|
||||
g_value_set_double (value, options->radius);
|
||||
break;
|
||||
case PROP_OPAQUE:
|
||||
g_value_set_double (value, options->opaque);
|
||||
break;
|
||||
case PROP_HARDNESS:
|
||||
g_value_set_double (value, options->hardness);
|
||||
break;
|
||||
@ -137,4 +210,13 @@ gimp_mybrush_options_get_property (GObject *object,
|
||||
}
|
||||
}
|
||||
|
||||
const gchar *
|
||||
gimp_mybrush_options_get_brush_data (GimpMybrushOptions *options)
|
||||
{
|
||||
OptionsState *state = g_hash_table_lookup (loaded_myb, options->mybrush);
|
||||
if (state)
|
||||
return state->brush_json;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -39,6 +39,7 @@ struct _GimpMybrushOptions
|
||||
GimpPaintOptions parent_instance;
|
||||
|
||||
gdouble radius;
|
||||
gdouble opaque;
|
||||
gdouble hardness;
|
||||
gchar *mybrush;
|
||||
};
|
||||
@ -51,6 +52,8 @@ struct _GimpMybrushOptionsClass
|
||||
|
||||
GType gimp_mybrush_options_get_type (void) G_GNUC_CONST;
|
||||
|
||||
const gchar *
|
||||
gimp_mybrush_options_get_brush_data (GimpMybrushOptions *options);
|
||||
|
||||
#endif /* __GIMP_MYBRUSH_OPTIONS_H__ */
|
||||
|
||||
|
453
app/paint/gimpmybrushsurface.c
Normal file
453
app/paint/gimpmybrushsurface.c
Normal file
@ -0,0 +1,453 @@
|
||||
/* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef HAVE_LIBMYPAINT
|
||||
|
||||
#include <gegl.h>
|
||||
#include "gimpmybrushsurface.h"
|
||||
#include <mypaint-surface.h>
|
||||
#include <math.h>
|
||||
|
||||
struct _GimpMybrushSurface
|
||||
{
|
||||
MyPaintSurface surface;
|
||||
GeglBuffer *buffer;
|
||||
GeglRectangle dirty;
|
||||
GimpComponentMask component_mask;
|
||||
};
|
||||
|
||||
/* --- Taken from mypaint-tiled-surface.c --- */
|
||||
static inline float
|
||||
calculate_rr (int xp,
|
||||
int yp,
|
||||
float x,
|
||||
float y,
|
||||
float aspect_ratio,
|
||||
float sn,
|
||||
float cs,
|
||||
float one_over_radius2)
|
||||
{
|
||||
/* code duplication, see brush::count_dabs_to() */
|
||||
const float yy = (yp + 0.5f - y);
|
||||
const float xx = (xp + 0.5f - x);
|
||||
const float yyr=(yy*cs-xx*sn)*aspect_ratio;
|
||||
const float xxr=yy*sn+xx*cs;
|
||||
const float rr = (yyr*yyr + xxr*xxr) * one_over_radius2;
|
||||
/* rr is in range 0.0..1.0*sqrt(2) */
|
||||
return rr;
|
||||
}
|
||||
|
||||
static inline float
|
||||
calculate_r_sample (float x,
|
||||
float y,
|
||||
float aspect_ratio,
|
||||
float sn,
|
||||
float cs)
|
||||
{
|
||||
const float yyr=(y*cs-x*sn)*aspect_ratio;
|
||||
const float xxr=y*sn+x*cs;
|
||||
const float r = (yyr*yyr + xxr*xxr);
|
||||
return r;
|
||||
}
|
||||
|
||||
static inline float
|
||||
sign_point_in_line (float px,
|
||||
float py,
|
||||
float vx,
|
||||
float vy)
|
||||
{
|
||||
return (px - vx) * (-vy) - (vx) * (py - vy);
|
||||
}
|
||||
|
||||
static inline void
|
||||
closest_point_to_line (float lx,
|
||||
float ly,
|
||||
float px,
|
||||
float py,
|
||||
float *ox,
|
||||
float *oy)
|
||||
{
|
||||
const float l2 = lx*lx + ly*ly;
|
||||
const float ltp_dot = px*lx + py*ly;
|
||||
const float t = ltp_dot / l2;
|
||||
*ox = lx * t;
|
||||
*oy = ly * t;
|
||||
}
|
||||
|
||||
|
||||
/* This works by taking the visibility at the nearest point
|
||||
* and dividing by 1.0 + delta.
|
||||
*
|
||||
* - nearest point: point where the dab has more influence
|
||||
* - farthest point: point at a fixed distance away from
|
||||
* the nearest point
|
||||
* - delta: how much occluded is the farthest point relative
|
||||
* to the nearest point
|
||||
*/
|
||||
static inline float
|
||||
calculate_rr_antialiased (int xp,
|
||||
int yp,
|
||||
float x,
|
||||
float y,
|
||||
float aspect_ratio,
|
||||
float sn,
|
||||
float cs,
|
||||
float one_over_radius2,
|
||||
float r_aa_start)
|
||||
{
|
||||
/* calculate pixel position and borders in a way
|
||||
* that the dab's center is always at zero */
|
||||
float pixel_right = x - (float)xp;
|
||||
float pixel_bottom = y - (float)yp;
|
||||
float pixel_center_x = pixel_right - 0.5f;
|
||||
float pixel_center_y = pixel_bottom - 0.5f;
|
||||
float pixel_left = pixel_right - 1.0f;
|
||||
float pixel_top = pixel_bottom - 1.0f;
|
||||
|
||||
float nearest_x, nearest_y; /* nearest to origin, but still inside pixel */
|
||||
float farthest_x, farthest_y; /* farthest from origin, but still inside pixel */
|
||||
float r_near, r_far, rr_near, rr_far;
|
||||
float center_sign, rad_area_1, visibilityNear, delta, delta2;
|
||||
|
||||
/* Dab's center is inside pixel? */
|
||||
if( pixel_left<0 && pixel_right>0 &&
|
||||
pixel_top<0 && pixel_bottom>0 )
|
||||
{
|
||||
nearest_x = 0;
|
||||
nearest_y = 0;
|
||||
r_near = rr_near = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
closest_point_to_line( cs, sn, pixel_center_x, pixel_center_y, &nearest_x, &nearest_y );
|
||||
nearest_x = CLAMP( nearest_x, pixel_left, pixel_right );
|
||||
nearest_y = CLAMP( nearest_y, pixel_top, pixel_bottom );
|
||||
/* XXX: precision of "nearest" values could be improved
|
||||
* by intersecting the line that goes from nearest_x/Y to 0
|
||||
* with the pixel's borders here, however the improvements
|
||||
* would probably not justify the perdormance cost.
|
||||
*/
|
||||
r_near = calculate_r_sample( nearest_x, nearest_y, aspect_ratio, sn, cs );
|
||||
rr_near = r_near * one_over_radius2;
|
||||
}
|
||||
|
||||
/* out of dab's reach? */
|
||||
if( rr_near > 1.0f )
|
||||
return rr_near;
|
||||
|
||||
/* check on which side of the dab's line is the pixel center */
|
||||
center_sign = sign_point_in_line( pixel_center_x, pixel_center_y, cs, -sn );
|
||||
|
||||
/* radius of a circle with area=1
|
||||
* A = pi * r * r
|
||||
* r = sqrt(1/pi)
|
||||
*/
|
||||
rad_area_1 = sqrtf( 1.0f / M_PI );
|
||||
|
||||
/* center is below dab */
|
||||
if( center_sign < 0 )
|
||||
{
|
||||
farthest_x = nearest_x - sn*rad_area_1;
|
||||
farthest_y = nearest_y + cs*rad_area_1;
|
||||
}
|
||||
/* above dab */
|
||||
else
|
||||
{
|
||||
farthest_x = nearest_x + sn*rad_area_1;
|
||||
farthest_y = nearest_y - cs*rad_area_1;
|
||||
}
|
||||
|
||||
r_far = calculate_r_sample( farthest_x, farthest_y, aspect_ratio, sn, cs );
|
||||
rr_far = r_far * one_over_radius2;
|
||||
|
||||
/* check if we can skip heavier AA */
|
||||
if( r_far < r_aa_start )
|
||||
return (rr_far+rr_near) * 0.5f;
|
||||
|
||||
/* calculate AA approximate */
|
||||
visibilityNear = 1.0f - rr_near;
|
||||
delta = rr_far - rr_near;
|
||||
delta2 = 1.0f + delta;
|
||||
visibilityNear /= delta2;
|
||||
|
||||
return 1.0f - visibilityNear;
|
||||
}
|
||||
/* -- end mypaint code */
|
||||
|
||||
static inline float
|
||||
calculate_alpha_for_rr (float rr,
|
||||
float hardness,
|
||||
float slope1,
|
||||
float slope2)
|
||||
{
|
||||
if (rr > 1.0f)
|
||||
return 0.0f;
|
||||
else if (rr <= hardness)
|
||||
return 1.0f + rr * slope1;
|
||||
else
|
||||
return rr * slope2 - slope2;
|
||||
}
|
||||
|
||||
static GeglRectangle
|
||||
calculate_dab_roi (float x,
|
||||
float y,
|
||||
float radius)
|
||||
{
|
||||
int x0 = floor (x - radius);
|
||||
int x1 = ceil (x + radius);
|
||||
int y0 = floor (y - radius);
|
||||
int y1 = ceil (y + radius);
|
||||
|
||||
return *GEGL_RECTANGLE (x0, y0, x1 - x0, y1 - y0);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
GimpMybrushSurface *surface = (GimpMybrushSurface *)base_surface;
|
||||
GeglRectangle dabRect;
|
||||
|
||||
if (radius < 1.0f)
|
||||
radius = 1.0f;
|
||||
|
||||
dabRect = calculate_dab_roi (x, y, radius);
|
||||
|
||||
*color_r = 0.0f;
|
||||
*color_g = 0.0f;
|
||||
*color_b = 0.0f;
|
||||
*color_a = 0.0f;
|
||||
|
||||
if (dabRect.width > 0 || dabRect.height > 0)
|
||||
{
|
||||
const float one_over_radius2 = 1.0f / (radius * radius);
|
||||
float sum_weight = 0.0f;
|
||||
float sum_r = 0.0f;
|
||||
float sum_g = 0.0f;
|
||||
float sum_b = 0.0f;
|
||||
float sum_a = 0.0f;
|
||||
|
||||
/* Read in clamp mode to avoid transparency bleeding in at the edges */
|
||||
GeglBufferIterator *iter = gegl_buffer_iterator_new (surface->buffer, &dabRect, 0,
|
||||
babl_format ("R'aG'aB'aA float"),
|
||||
GEGL_BUFFER_READ,
|
||||
GEGL_ABYSS_CLAMP);
|
||||
while (gegl_buffer_iterator_next (iter))
|
||||
{
|
||||
float *pixel = (float *)iter->data[0];
|
||||
int iy, ix;
|
||||
|
||||
for (iy = iter->roi[0].y; iy < iter->roi[0].y + iter->roi[0].height; iy++)
|
||||
{
|
||||
float yy = (iy + 0.5f - y);
|
||||
for (ix = iter->roi[0].x; ix < iter->roi[0].x + iter->roi[0].width; ix++)
|
||||
{
|
||||
/* pixel_weight == a standard dab with hardness = 0.5, aspect_ratio = 1.0, and angle = 0.0 */
|
||||
float xx = (ix + 0.5f - x);
|
||||
float rr = (yy * yy + xx * xx) * one_over_radius2;
|
||||
float pixel_weight = 0.0f;
|
||||
if (rr <= 1.0f)
|
||||
pixel_weight = 1.0f - rr;
|
||||
|
||||
sum_r += pixel_weight * pixel[RED];
|
||||
sum_g += pixel_weight * pixel[GREEN];
|
||||
sum_b += pixel_weight * pixel[BLUE];
|
||||
sum_a += pixel_weight * pixel[ALPHA];
|
||||
sum_weight += pixel_weight;
|
||||
|
||||
pixel += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sum_a > 0.0f && sum_weight > 0.0f)
|
||||
{
|
||||
sum_r /= sum_weight;
|
||||
sum_g /= sum_weight;
|
||||
sum_b /= sum_weight;
|
||||
sum_a /= sum_weight;
|
||||
|
||||
sum_r /= sum_a;
|
||||
sum_g /= sum_a;
|
||||
sum_b /= sum_a;
|
||||
|
||||
/* FIXME: Clamping is wrong because GEGL allows alpha > 1, this should probably re-multipy things */
|
||||
*color_r = CLAMP(sum_r, 0.0f, 1.0f);
|
||||
*color_g = CLAMP(sum_g, 0.0f, 1.0f);
|
||||
*color_b = CLAMP(sum_b, 0.0f, 1.0f);
|
||||
*color_a = CLAMP(sum_a, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
GimpMybrushSurface *surface = (GimpMybrushSurface *)base_surface;
|
||||
GeglBufferIterator *iter;
|
||||
GeglRectangle dabRect;
|
||||
GimpComponentMask component_mask = surface->component_mask;
|
||||
|
||||
const float one_over_radius2 = 1.0f / (radius * radius);
|
||||
const double angle_rad = angle / 360 * 2 * M_PI;
|
||||
const float cs = cos(angle_rad);
|
||||
const float sn = sin(angle_rad);
|
||||
float segment1_slope;
|
||||
float segment2_slope;
|
||||
float r_aa_start;
|
||||
float mixbuf[4];
|
||||
|
||||
hardness = CLAMP (hardness, 0.0f, 1.0f);
|
||||
segment1_slope = -(1.0f / hardness - 1.0f);
|
||||
segment2_slope = -hardness / (1.0f - hardness);
|
||||
aspect_ratio = MAX (1.0f, aspect_ratio);
|
||||
|
||||
r_aa_start = radius - 1.0f;
|
||||
r_aa_start = MAX (r_aa_start, 0);
|
||||
r_aa_start = (r_aa_start * r_aa_start) / aspect_ratio;
|
||||
|
||||
/* FIXME: This should use the real matrix values to trim aspect_ratio dabs */
|
||||
dabRect = calculate_dab_roi (x, y, radius);
|
||||
gegl_rectangle_intersect (&dabRect, &dabRect, gegl_buffer_get_extent (surface->buffer));
|
||||
|
||||
if (dabRect.width <= 0 || dabRect.height <= 0)
|
||||
return 0;
|
||||
|
||||
gegl_rectangle_bounding_box (&surface->dirty, &surface->dirty, &dabRect);
|
||||
|
||||
iter = gegl_buffer_iterator_new (surface->buffer, &dabRect, 0,
|
||||
babl_format ("R'G'B'A float"),
|
||||
GEGL_BUFFER_READWRITE,
|
||||
GEGL_ABYSS_NONE);
|
||||
|
||||
while (gegl_buffer_iterator_next (iter))
|
||||
{
|
||||
float *pixel = (float *)iter->data[0];
|
||||
int iy, ix;
|
||||
for (iy = iter->roi[0].y; iy < iter->roi[0].y + iter->roi[0].height; iy++)
|
||||
{
|
||||
for (ix = iter->roi[0].x; ix < iter->roi[0].x + iter->roi[0].width; ix++)
|
||||
{
|
||||
float rr, alpha, dst_alpha, a;
|
||||
float *dst_pixel = (component_mask != GIMP_COMPONENT_MASK_ALL) ? mixbuf : pixel;
|
||||
if (radius < 3.0f)
|
||||
rr = calculate_rr_antialiased (ix, iy, x, y, aspect_ratio, sn, cs, one_over_radius2, r_aa_start);
|
||||
else
|
||||
rr = calculate_rr (ix, iy, x, y, aspect_ratio, sn, cs, one_over_radius2);
|
||||
alpha = calculate_alpha_for_rr (rr, hardness, segment1_slope, segment2_slope) * opaque;
|
||||
dst_alpha = pixel[ALPHA];
|
||||
a = alpha * color_a + dst_alpha * (1.0f - alpha);
|
||||
|
||||
if (a > 0.0f)
|
||||
{
|
||||
float a_term = dst_alpha * (1.0f - alpha);
|
||||
dst_pixel[RED] = (color_r * alpha * color_a + pixel[RED] * a_term) / a;
|
||||
dst_pixel[GREEN] = (color_g * alpha * color_a + pixel[GREEN] * a_term) / a;
|
||||
dst_pixel[BLUE] = (color_b * alpha * color_a + pixel[BLUE] * a_term) / a;
|
||||
}
|
||||
dst_pixel[ALPHA] = a;
|
||||
|
||||
/* FIXME: Implement mypaint style lock-alpha mode */
|
||||
/* FIXME: Implement colorize mode */
|
||||
|
||||
if (component_mask != GIMP_COMPONENT_MASK_ALL)
|
||||
{
|
||||
if (component_mask & GIMP_COMPONENT_MASK_RED)
|
||||
pixel[RED] = mixbuf[RED];
|
||||
if (component_mask & GIMP_COMPONENT_MASK_GREEN)
|
||||
pixel[GREEN] = mixbuf[GREEN];
|
||||
if (component_mask & GIMP_COMPONENT_MASK_BLUE)
|
||||
pixel[BLUE] = mixbuf[BLUE];
|
||||
if (component_mask & GIMP_COMPONENT_MASK_ALPHA)
|
||||
pixel[ALPHA] = mixbuf[ALPHA];
|
||||
}
|
||||
pixel += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
gimp_mypaint_surface_begin_atomic (MyPaintSurface *base_surface)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
gimp_mypaint_surface_end_atomic (MyPaintSurface *base_surface,
|
||||
MyPaintRectangle *roi)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
static void
|
||||
gimp_mypaint_surface_destroy (MyPaintSurface *base_surface)
|
||||
{
|
||||
GimpMybrushSurface *surface = (GimpMybrushSurface *)base_surface;
|
||||
g_object_unref (surface->buffer);
|
||||
surface->buffer = NULL;
|
||||
}
|
||||
|
||||
GimpMybrushSurface *
|
||||
gimp_mypaint_surface_new (GeglBuffer *buffer,
|
||||
GimpComponentMask component_mask)
|
||||
{
|
||||
GimpMybrushSurface *surface = g_malloc0 (sizeof (GimpMybrushSurface));
|
||||
mypaint_surface_init ((MyPaintSurface *)surface);
|
||||
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->buffer = g_object_ref (buffer);
|
||||
surface->dirty = *GEGL_RECTANGLE (0, 0, 0, 0);
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
||||
#endif
|
35
app/paint/gimpmybrushsurface.h
Normal file
35
app/paint/gimpmybrushsurface.h
Normal file
@ -0,0 +1,35 @@
|
||||
/* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_LIBMYPAINT
|
||||
|
||||
#ifndef __GIMP_MYBRUSH_SURFACE_H__
|
||||
#define __GIMP_MYBRUSH_SURFACE_H__
|
||||
|
||||
#include "paint-types.h"
|
||||
#include "core/gimpobject.h"
|
||||
|
||||
typedef struct _GimpMybrushSurface GimpMybrushSurface;
|
||||
|
||||
GimpMybrushSurface *
|
||||
gimp_mypaint_surface_new (GeglBuffer *buffer,
|
||||
GimpComponentMask component_mask);
|
||||
|
||||
|
||||
#endif /* __GIMP_MYBRUSH_SURFACE_H__ */
|
||||
|
||||
#endif
|
@ -168,6 +168,13 @@ gimp_mybrush_options_gui (GimpToolOptions *tool_options)
|
||||
gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
|
||||
gtk_widget_show (scale);
|
||||
|
||||
/* opaque */
|
||||
scale = gimp_prop_spin_scale_new (config, "opaque",
|
||||
_("Base Opacity"),
|
||||
0.1, 1.0, 2);
|
||||
gtk_box_pack_start (GTK_BOX (vbox), scale, FALSE, FALSE, 0);
|
||||
gtk_widget_show (scale);
|
||||
|
||||
/* hardness */
|
||||
scale = gimp_prop_spin_scale_new (config, "hardness",
|
||||
_("Hardness"),
|
||||
|
Reference in New Issue
Block a user