Commit df21e078 authored by Daniel Sabo's avatar Daniel Sabo

app: Fix MyPaint brush rendering, implement MyPaintSurface in a native format

parent bcbc83dc
......@@ -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__ */
......
/* 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++)