app: improve gimp_gegl_mask_combine_ellipse[_rect]()
Improve gimp_gegl_mask_combine_ellipse_rect() -- the funciton responsible for rendering ellipse/rounded-rectangle selections. Most notably, this commit significantly improves the function's performance, by identifying whole tiles, whole rows, or parts of a row, that are fully inside, or fully outside, the ellipse, and filling them in bulk, instead of calculating the anti-aliasing value at each pixel, which is now only done along the circumference. This commit also improves anti-aliasing, by more accurately approximating the distance from a pixel to the ellipse, and by normalizing the distance according to the pixel's cross-section length in the direction of the said point. In particular, we guarantee that pixels that are fully inside/outside the ellipse have a value of 1/0, respectively, facilitating the aforementioned optimization. Additionally, this commit fixes various edge cases where several primitives coincide at a single pixel (in the rounded-rectangle case), adds support for CHANNEL_OP_INTERSECT, and parallelizes processing.
This commit is contained in:
@ -89,24 +89,6 @@ gimp_channel_combine_rect (GimpChannel *mask,
|
|||||||
gimp_drawable_update (GIMP_DRAWABLE (mask), x, y, w, h);
|
gimp_drawable_update (GIMP_DRAWABLE (mask), x, y, w, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* gimp_channel_combine_ellipse:
|
|
||||||
* @mask: the channel with which to combine the ellipse
|
|
||||||
* @op: whether to replace, add to, or subtract from the current
|
|
||||||
* contents
|
|
||||||
* @x: x coordinate of upper left corner of ellipse
|
|
||||||
* @y: y coordinate of upper left corner of ellipse
|
|
||||||
* @w: width of ellipse bounding box
|
|
||||||
* @h: height of ellipse bounding box
|
|
||||||
* @antialias: if %TRUE, antialias the ellipse
|
|
||||||
*
|
|
||||||
* Mainly used for elliptical selections. If @op is
|
|
||||||
* %GIMP_CHANNEL_OP_REPLACE or %GIMP_CHANNEL_OP_ADD, sets pixels
|
|
||||||
* within the ellipse to 255. If @op is %GIMP_CHANNEL_OP_SUBTRACT,
|
|
||||||
* sets pixels within to zero. If @antialias is %TRUE, pixels that
|
|
||||||
* impinge on the edge of the ellipse are set to intermediate values,
|
|
||||||
* depending on how much they overlap.
|
|
||||||
**/
|
|
||||||
void
|
void
|
||||||
gimp_channel_combine_ellipse (GimpChannel *mask,
|
gimp_channel_combine_ellipse (GimpChannel *mask,
|
||||||
GimpChannelOps op,
|
GimpChannelOps op,
|
||||||
@ -120,26 +102,6 @@ gimp_channel_combine_ellipse (GimpChannel *mask,
|
|||||||
w / 2.0, h / 2.0, antialias);
|
w / 2.0, h / 2.0, antialias);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* gimp_channel_combine_ellipse_rect:
|
|
||||||
* @mask: the channel with which to combine the elliptic rect
|
|
||||||
* @op: whether to replace, add to, or subtract from the current
|
|
||||||
* contents
|
|
||||||
* @x: x coordinate of upper left corner of bounding rect
|
|
||||||
* @y: y coordinate of upper left corner of bounding rect
|
|
||||||
* @w: width of bounding rect
|
|
||||||
* @h: height of bounding rect
|
|
||||||
* @a: elliptic a-constant applied to corners
|
|
||||||
* @b: elliptic b-constant applied to corners
|
|
||||||
* @antialias: if %TRUE, antialias the elliptic corners
|
|
||||||
*
|
|
||||||
* Used for rounded cornered rectangles and ellipses. If @op is
|
|
||||||
* %GIMP_CHANNEL_OP_REPLACE or %GIMP_CHANNEL_OP_ADD, sets pixels
|
|
||||||
* within the ellipse to 255. If @op is %GIMP_CHANNEL_OP_SUBTRACT,
|
|
||||||
* sets pixels within to zero. If @antialias is %TRUE, pixels that
|
|
||||||
* impinge on the edge of the ellipse are set to intermediate values,
|
|
||||||
* depending on how much they overlap.
|
|
||||||
**/
|
|
||||||
void
|
void
|
||||||
gimp_channel_combine_ellipse_rect (GimpChannel *mask,
|
gimp_channel_combine_ellipse_rect (GimpChannel *mask,
|
||||||
GimpChannelOps op,
|
GimpChannelOps op,
|
||||||
@ -147,20 +109,19 @@ gimp_channel_combine_ellipse_rect (GimpChannel *mask,
|
|||||||
gint y,
|
gint y,
|
||||||
gint w,
|
gint w,
|
||||||
gint h,
|
gint h,
|
||||||
gdouble a,
|
gdouble rx,
|
||||||
gdouble b,
|
gdouble ry,
|
||||||
gboolean antialias)
|
gboolean antialias)
|
||||||
{
|
{
|
||||||
GeglBuffer *buffer;
|
GeglBuffer *buffer;
|
||||||
|
|
||||||
g_return_if_fail (GIMP_IS_CHANNEL (mask));
|
g_return_if_fail (GIMP_IS_CHANNEL (mask));
|
||||||
g_return_if_fail (a >= 0.0 && b >= 0.0);
|
|
||||||
g_return_if_fail (op != GIMP_CHANNEL_OP_INTERSECT);
|
g_return_if_fail (op != GIMP_CHANNEL_OP_INTERSECT);
|
||||||
|
|
||||||
buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
|
buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (mask));
|
||||||
|
|
||||||
if (! gimp_gegl_mask_combine_ellipse_rect (buffer, op, x, y, w, h,
|
if (! gimp_gegl_mask_combine_ellipse_rect (buffer, op, x, y, w, h,
|
||||||
a, b, antialias))
|
rx, ry, antialias))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
gimp_rectangle_intersect (x, y, w, h,
|
gimp_rectangle_intersect (x, y, w, h,
|
||||||
|
@ -38,8 +38,8 @@ void gimp_channel_combine_ellipse_rect (GimpChannel *mask,
|
|||||||
gint y,
|
gint y,
|
||||||
gint w,
|
gint w,
|
||||||
gint h,
|
gint h,
|
||||||
gdouble a,
|
gdouble rx,
|
||||||
gdouble b,
|
gdouble ry,
|
||||||
gboolean antialias);
|
gboolean antialias);
|
||||||
void gimp_channel_combine_mask (GimpChannel *mask,
|
void gimp_channel_combine_mask (GimpChannel *mask,
|
||||||
GimpChannel *add_on,
|
GimpChannel *add_on,
|
||||||
|
@ -34,6 +34,12 @@ extern "C"
|
|||||||
#include "gimp-gegl-mask-combine.h"
|
#include "gimp-gegl-mask-combine.h"
|
||||||
|
|
||||||
|
|
||||||
|
#define EPSILON 1e-6
|
||||||
|
|
||||||
|
#define PIXELS_PER_THREAD \
|
||||||
|
(/* each thread costs as much as */ 64.0 * 64.0 /* pixels */)
|
||||||
|
|
||||||
|
|
||||||
gboolean
|
gboolean
|
||||||
gimp_gegl_mask_combine_rect (GeglBuffer *mask,
|
gimp_gegl_mask_combine_rect (GeglBuffer *mask,
|
||||||
GimpChannelOps op,
|
GimpChannelOps op,
|
||||||
@ -75,24 +81,6 @@ gimp_gegl_mask_combine_rect (GeglBuffer *mask,
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* gimp_gegl_mask_combine_ellipse:
|
|
||||||
* @mask: the channel with which to combine the ellipse
|
|
||||||
* @op: whether to replace, add to, or subtract from the current
|
|
||||||
* contents
|
|
||||||
* @x: x coordinate of upper left corner of ellipse
|
|
||||||
* @y: y coordinate of upper left corner of ellipse
|
|
||||||
* @w: width of ellipse bounding box
|
|
||||||
* @h: height of ellipse bounding box
|
|
||||||
* @antialias: if %TRUE, antialias the ellipse
|
|
||||||
*
|
|
||||||
* Mainly used for elliptical selections. If @op is
|
|
||||||
* %GIMP_CHANNEL_OP_REPLACE or %GIMP_CHANNEL_OP_ADD, sets pixels
|
|
||||||
* within the ellipse to 255. If @op is %GIMP_CHANNEL_OP_SUBTRACT,
|
|
||||||
* sets pixels within to zero. If @antialias is %TRUE, pixels that
|
|
||||||
* impinge on the edge of the ellipse are set to intermediate values,
|
|
||||||
* depending on how much they overlap.
|
|
||||||
**/
|
|
||||||
gboolean
|
gboolean
|
||||||
gimp_gegl_mask_combine_ellipse (GeglBuffer *mask,
|
gimp_gegl_mask_combine_ellipse (GeglBuffer *mask,
|
||||||
GimpChannelOps op,
|
GimpChannelOps op,
|
||||||
@ -106,77 +94,6 @@ gimp_gegl_mask_combine_ellipse (GeglBuffer *mask,
|
|||||||
w / 2.0, h / 2.0, antialias);
|
w / 2.0, h / 2.0, antialias);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
|
||||||
gimp_gegl_mask_combine_span (gfloat *data,
|
|
||||||
GimpChannelOps op,
|
|
||||||
gint x1,
|
|
||||||
gint x2,
|
|
||||||
gfloat value)
|
|
||||||
{
|
|
||||||
if (x2 <= x1)
|
|
||||||
return;
|
|
||||||
|
|
||||||
switch (op)
|
|
||||||
{
|
|
||||||
case GIMP_CHANNEL_OP_ADD:
|
|
||||||
case GIMP_CHANNEL_OP_REPLACE:
|
|
||||||
if (value == 1.0)
|
|
||||||
{
|
|
||||||
while (x1 < x2)
|
|
||||||
data[x1++] = 1.0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
while (x1 < x2)
|
|
||||||
{
|
|
||||||
const gfloat val = data[x1] + value;
|
|
||||||
data[x1++] = val > 1.0 ? 1.0 : val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GIMP_CHANNEL_OP_SUBTRACT:
|
|
||||||
if (value == 1.0)
|
|
||||||
{
|
|
||||||
while (x1 < x2)
|
|
||||||
data[x1++] = 0.0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
while (x1 < x2)
|
|
||||||
{
|
|
||||||
const gfloat val = data[x1] - value;
|
|
||||||
data[x1++] = val > 0.0 ? val : 0.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GIMP_CHANNEL_OP_INTERSECT:
|
|
||||||
/* Should not happen */
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* gimp_gegl_mask_combine_ellipse_rect:
|
|
||||||
* @mask: the channel with which to combine the elliptic rect
|
|
||||||
* @op: whether to replace, add to, or subtract from the current
|
|
||||||
* contents
|
|
||||||
* @x: x coordinate of upper left corner of bounding rect
|
|
||||||
* @y: y coordinate of upper left corner of bounding rect
|
|
||||||
* @w: width of bounding rect
|
|
||||||
* @h: height of bounding rect
|
|
||||||
* @a: elliptic a-constant applied to corners
|
|
||||||
* @b: elliptic b-constant applied to corners
|
|
||||||
* @antialias: if %TRUE, antialias the elliptic corners
|
|
||||||
*
|
|
||||||
* Used for rounded cornered rectangles and ellipses. If @op is
|
|
||||||
* %GIMP_CHANNEL_OP_REPLACE or %GIMP_CHANNEL_OP_ADD, sets pixels
|
|
||||||
* within the ellipse to 255. If @op is %GIMP_CHANNEL_OP_SUBTRACT,
|
|
||||||
* sets pixels within to zero. If @antialias is %TRUE, pixels that
|
|
||||||
* impinge on the edge of the ellipse are set to intermediate values,
|
|
||||||
* depending on how much they overlap.
|
|
||||||
**/
|
|
||||||
gboolean
|
gboolean
|
||||||
gimp_gegl_mask_combine_ellipse_rect (GeglBuffer *mask,
|
gimp_gegl_mask_combine_ellipse_rect (GeglBuffer *mask,
|
||||||
GimpChannelOps op,
|
GimpChannelOps op,
|
||||||
@ -184,200 +101,399 @@ gimp_gegl_mask_combine_ellipse_rect (GeglBuffer *mask,
|
|||||||
gint y,
|
gint y,
|
||||||
gint w,
|
gint w,
|
||||||
gint h,
|
gint h,
|
||||||
gdouble a,
|
gdouble rx,
|
||||||
gdouble b,
|
gdouble ry,
|
||||||
gboolean antialias)
|
gboolean antialias)
|
||||||
{
|
{
|
||||||
GeglBufferIterator *iter;
|
GeglRectangle rect;
|
||||||
GeglRectangle *roi;
|
const Babl *format;
|
||||||
gdouble a_sqr;
|
gint bpp;
|
||||||
gdouble b_sqr;
|
gfloat one_f = 1.0f;
|
||||||
gdouble ellipse_center_x;
|
gpointer one;
|
||||||
gint x0, y0;
|
gdouble cx;
|
||||||
gint width, height;
|
gdouble cy;
|
||||||
|
gint left;
|
||||||
|
gint right;
|
||||||
|
gint top;
|
||||||
|
gint bottom;
|
||||||
|
|
||||||
g_return_val_if_fail (GEGL_IS_BUFFER (mask), FALSE);
|
g_return_val_if_fail (GEGL_IS_BUFFER (mask), FALSE);
|
||||||
g_return_val_if_fail (a >= 0.0 && b >= 0.0, FALSE);
|
|
||||||
g_return_val_if_fail (op != GIMP_CHANNEL_OP_INTERSECT, FALSE);
|
|
||||||
|
|
||||||
/* Make sure the elliptic corners fit into the rect */
|
if (rx <= EPSILON || ry <= EPSILON)
|
||||||
a = MIN (a, w / 2.0);
|
return gimp_gegl_mask_combine_rect (mask, op, x, y, w, h);
|
||||||
b = MIN (b, h / 2.0);
|
|
||||||
|
|
||||||
a_sqr = SQR (a);
|
left = x;
|
||||||
b_sqr = SQR (b);
|
right = x + w;
|
||||||
|
top = y;
|
||||||
|
bottom = y + h;
|
||||||
|
|
||||||
if (! gimp_rectangle_intersect (x, y, w, h,
|
cx = (left + right) / 2.0;
|
||||||
0, 0,
|
cy = (top + bottom) / 2.0;
|
||||||
gegl_buffer_get_width (mask),
|
|
||||||
gegl_buffer_get_height (mask),
|
|
||||||
&x0, &y0, &width, &height))
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
ellipse_center_x = x + a;
|
rx = MIN (rx, w / 2.0);
|
||||||
|
ry = MIN (ry, h / 2.0);
|
||||||
|
|
||||||
iter = gegl_buffer_iterator_new (mask,
|
if (! gegl_rectangle_intersect (&rect,
|
||||||
GEGL_RECTANGLE (x0, y0, width, height), 0,
|
GEGL_RECTANGLE (x, y, w, h),
|
||||||
babl_format ("Y float"),
|
gegl_buffer_get_abyss (mask)))
|
||||||
GEGL_ACCESS_READWRITE, GEGL_ABYSS_NONE, 1);
|
|
||||||
roi = &iter->items[0].roi;
|
|
||||||
|
|
||||||
while (gegl_buffer_iterator_next (iter))
|
|
||||||
{
|
{
|
||||||
gfloat *data = (gfloat *) iter->items[0].data;
|
return FALSE;
|
||||||
gint py;
|
}
|
||||||
|
|
||||||
for (py = roi->y;
|
format = gegl_buffer_get_format (mask);
|
||||||
py < roi->y + roi->height;
|
|
||||||
py++, data += roi->width)
|
if (antialias)
|
||||||
|
{
|
||||||
|
format = gimp_babl_format_change_component_type (
|
||||||
|
format, GIMP_COMPONENT_TYPE_FLOAT);
|
||||||
|
}
|
||||||
|
|
||||||
|
bpp = babl_format_get_bytes_per_pixel (format);
|
||||||
|
one = g_alloca (bpp);
|
||||||
|
|
||||||
|
babl_process (babl_fish ("Y float", format), &one_f, one, 1);
|
||||||
|
|
||||||
|
/* coordinate-system transforms. (x, y) coordinates are in the image
|
||||||
|
* coordinate-system, and (u, v) coordinates are in a coordinate-system
|
||||||
|
* aligned with the center of one of the elliptic corners, with the positive
|
||||||
|
* directions pointing away from the rectangle. when converting from (x, y)
|
||||||
|
* to (u, v), we use the closest elliptic corner.
|
||||||
|
*/
|
||||||
|
auto x_to_u = [=] (gdouble x)
|
||||||
|
{
|
||||||
|
if (x < cx)
|
||||||
|
return (left + rx) - x;
|
||||||
|
else
|
||||||
|
return x - (right - rx);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto y_to_v = [=] (gdouble y)
|
||||||
|
{
|
||||||
|
if (y < cy)
|
||||||
|
return (top + ry) - y;
|
||||||
|
else
|
||||||
|
return y - (bottom - ry);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto u_to_x_left = [=] (gdouble u)
|
||||||
|
{
|
||||||
|
return (left + rx) - u;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto u_to_x_right = [=] (gdouble u)
|
||||||
|
{
|
||||||
|
return (right - rx) + u;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* intersection of a horizontal line with the ellipse */
|
||||||
|
auto v_to_u = [=] (gdouble v)
|
||||||
|
{
|
||||||
|
if (v > 0.0)
|
||||||
|
return sqrt (MAX (SQR (rx) - SQR (rx * v / ry), 0.0));
|
||||||
|
else
|
||||||
|
return rx;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* intersection of a vertical line with the ellipse */
|
||||||
|
auto u_to_v = [=] (gdouble u)
|
||||||
|
{
|
||||||
|
if (u > 0.0)
|
||||||
|
return sqrt (MAX (SQR (ry) - SQR (ry * u / rx), 0.0));
|
||||||
|
else
|
||||||
|
return ry;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* signed, normalized distance of a point from the ellipse's circumference.
|
||||||
|
* the sign of the result determines if the point is inside (positive) or
|
||||||
|
* outside (negative) the ellipse. the result is normalized to the cross-
|
||||||
|
* section length of a pixel, in the direction of the closest point along the
|
||||||
|
* ellipse.
|
||||||
|
*
|
||||||
|
* we use the following method to approximate the distance: pass horizontal
|
||||||
|
* and vertical lines at the given point, P, and find their (positive) points
|
||||||
|
* of intersection with the ellipse, A and B. the segment AB is an
|
||||||
|
* approximation of the corresponding elliptic arc (see bug #147836). find
|
||||||
|
* the closest point, C, to P, along the segment AB. find the (positive)
|
||||||
|
* point of intersection, Q, of the line PC and the ellipse. Q is an
|
||||||
|
* approximation for the closest point to P along the ellipse, and the
|
||||||
|
* approximated distance is the distance from P to Q.
|
||||||
|
*/
|
||||||
|
auto ellipse_distance = [=] (gdouble u,
|
||||||
|
gdouble v)
|
||||||
|
{
|
||||||
|
gdouble du;
|
||||||
|
gdouble dv;
|
||||||
|
gdouble t;
|
||||||
|
gdouble a, b, c;
|
||||||
|
gdouble d;
|
||||||
|
|
||||||
|
u = MAX (u, 0.0);
|
||||||
|
v = MAX (v, 0.0);
|
||||||
|
|
||||||
|
du = v_to_u (v) - u;
|
||||||
|
dv = u_to_v (u) - v;
|
||||||
|
|
||||||
|
t = SQR (du) / (SQR (du) + SQR (dv));
|
||||||
|
|
||||||
|
du *= 1.0 - t;
|
||||||
|
dv *= t;
|
||||||
|
|
||||||
|
v *= rx / ry;
|
||||||
|
dv *= rx / ry;
|
||||||
|
|
||||||
|
a = SQR (du) + SQR (dv);
|
||||||
|
b = u * du + v * dv;
|
||||||
|
c = SQR (u) + SQR (v) - SQR (rx);
|
||||||
|
|
||||||
|
if (a <= EPSILON)
|
||||||
|
return 0.0;
|
||||||
|
|
||||||
|
if (c < 0.0)
|
||||||
|
t = (-b + sqrt (MAX (SQR (b) - a * c, 0.0))) / a;
|
||||||
|
else
|
||||||
|
t = (-b - sqrt (MAX (SQR (b) - a * c, 0.0))) / a;
|
||||||
|
|
||||||
|
dv *= ry / rx;
|
||||||
|
|
||||||
|
d = sqrt (SQR (du * t) + SQR (dv * t));
|
||||||
|
|
||||||
|
if (c > 0.0)
|
||||||
|
d = -d;
|
||||||
|
|
||||||
|
d /= sqrt (SQR (MIN (du / dv, dv / du)) + 1.0);
|
||||||
|
|
||||||
|
return d;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* anti-aliased value of a pixel */
|
||||||
|
auto pixel_value = [=] (gint x,
|
||||||
|
gint y)
|
||||||
|
{
|
||||||
|
gdouble u = x_to_u (x + 0.5);
|
||||||
|
gdouble v = y_to_v (y + 0.5);
|
||||||
|
gdouble d = ellipse_distance (u, v);
|
||||||
|
|
||||||
|
/* use the distance of the pixel's center from the ellipse to approximate
|
||||||
|
* the coverage
|
||||||
|
*/
|
||||||
|
d = CLAMP (0.5 + d, 0.0, 1.0);
|
||||||
|
|
||||||
|
/* we're at the horizontal boundary of an elliptic corner */
|
||||||
|
if (u < 0.5)
|
||||||
|
d = d * (0.5 + u) + (0.5 - u);
|
||||||
|
|
||||||
|
/* we're at the vertical boundary of an elliptic corner */
|
||||||
|
if (v < 0.5)
|
||||||
|
d = d * (0.5 + v) + (0.5 - v);
|
||||||
|
|
||||||
|
/* opposite horizontal corners intersect the pixel */
|
||||||
|
if (x == (right - 1) - (x - left))
|
||||||
|
d = 2.0 * d - 1.0;
|
||||||
|
|
||||||
|
/* opposite vertical corners intersect the pixel */
|
||||||
|
if (y == (bottom - 1) - (y - top))
|
||||||
|
d = 2.0 * d - 1.0;
|
||||||
|
|
||||||
|
return d;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto ellipse_range = [=] (gdouble y,
|
||||||
|
gdouble *x0,
|
||||||
|
gdouble *x1)
|
||||||
|
{
|
||||||
|
gdouble u = v_to_u (y_to_v (y));
|
||||||
|
|
||||||
|
*x0 = u_to_x_left (u);
|
||||||
|
*x1 = u_to_x_right (u);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto fill0 = [=] (gpointer dest,
|
||||||
|
gint n)
|
||||||
|
{
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case GIMP_CHANNEL_OP_REPLACE:
|
||||||
|
case GIMP_CHANNEL_OP_INTERSECT:
|
||||||
|
memset (dest, 0, bpp * n);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GIMP_CHANNEL_OP_ADD:
|
||||||
|
case GIMP_CHANNEL_OP_SUBTRACT:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (gpointer) ((guint8 *) dest + bpp * n);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto fill1 = [=] (gpointer dest,
|
||||||
|
gint n)
|
||||||
|
{
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case GIMP_CHANNEL_OP_REPLACE:
|
||||||
|
case GIMP_CHANNEL_OP_ADD:
|
||||||
|
gegl_memset_pattern (dest, one, bpp, n);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GIMP_CHANNEL_OP_SUBTRACT:
|
||||||
|
memset (dest, 0, bpp * n);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GIMP_CHANNEL_OP_INTERSECT:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (gpointer) ((guint8 *) dest + bpp * n);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto set = [=] (gpointer dest,
|
||||||
|
gfloat value)
|
||||||
|
{
|
||||||
|
gfloat *p = (gfloat *) dest;
|
||||||
|
|
||||||
|
switch (op)
|
||||||
|
{
|
||||||
|
case GIMP_CHANNEL_OP_REPLACE:
|
||||||
|
*p = value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GIMP_CHANNEL_OP_ADD:
|
||||||
|
*p = MIN (*p + value, 1.0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GIMP_CHANNEL_OP_SUBTRACT:
|
||||||
|
*p = MAX (*p - value, 0.0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GIMP_CHANNEL_OP_INTERSECT:
|
||||||
|
*p = MIN (*p, value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (gpointer) (p + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
gegl_parallel_distribute_area (
|
||||||
|
&rect, PIXELS_PER_THREAD,
|
||||||
|
[=] (const GeglRectangle *area)
|
||||||
|
{
|
||||||
|
GeglBufferIterator *iter;
|
||||||
|
|
||||||
|
iter = gegl_buffer_iterator_new (
|
||||||
|
mask, area, 0, format,
|
||||||
|
op == GIMP_CHANNEL_OP_REPLACE ? GEGL_ACCESS_WRITE :
|
||||||
|
GEGL_ACCESS_READWRITE,
|
||||||
|
GEGL_ABYSS_NONE, 1);
|
||||||
|
|
||||||
|
while (gegl_buffer_iterator_next (iter))
|
||||||
{
|
{
|
||||||
const gint px = roi->x;
|
const GeglRectangle *roi = &iter->items[0].roi;
|
||||||
gdouble ellipse_center_y;
|
gpointer d = iter->items[0].data;
|
||||||
|
gdouble tx0, ty0;
|
||||||
|
gdouble tx1, ty1;
|
||||||
|
gdouble x0;
|
||||||
|
gdouble x1;
|
||||||
|
gint y;
|
||||||
|
|
||||||
if (py >= y + b && py < y + h - b)
|
/* tile bounds */
|
||||||
{
|
tx0 = roi->x;
|
||||||
/* we are on a row without rounded corners */
|
ty0 = roi->y;
|
||||||
gimp_gegl_mask_combine_span (data, op, 0, roi->width, 1.0);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Match the ellipse center y with our current y */
|
tx1 = roi->x + roi->width;
|
||||||
if (py < y + b)
|
ty1 = roi->y + roi->height;
|
||||||
{
|
|
||||||
ellipse_center_y = y + b;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ellipse_center_y = y + h - b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* For a non-antialiased ellipse, use the normal equation
|
|
||||||
* for an ellipse with an arbitrary center
|
|
||||||
* (ellipse_center_x, ellipse_center_y).
|
|
||||||
*/
|
|
||||||
if (! antialias)
|
if (! antialias)
|
||||||
{
|
{
|
||||||
gdouble half_ellipse_width_at_y;
|
tx0 += 0.5;
|
||||||
gint x_start;
|
ty0 += 0.5;
|
||||||
gint x_end;
|
|
||||||
|
|
||||||
half_ellipse_width_at_y =
|
tx1 -= 0.5;
|
||||||
sqrt (a_sqr -
|
ty1 -= 0.5;
|
||||||
a_sqr * SQR (py + 0.5f - ellipse_center_y) / b_sqr);
|
|
||||||
|
|
||||||
x_start = ROUND (ellipse_center_x - half_ellipse_width_at_y);
|
|
||||||
x_end = ROUND (ellipse_center_x + w - 2 * a +
|
|
||||||
half_ellipse_width_at_y);
|
|
||||||
|
|
||||||
gimp_gegl_mask_combine_span (data, op,
|
|
||||||
MAX (x_start - px, 0),
|
|
||||||
MIN (x_end - px, roi->width), 1.0);
|
|
||||||
}
|
}
|
||||||
else /* use antialiasing */
|
|
||||||
|
/* if the tile is fully inside/outside the ellipse, fill it with 1/0,
|
||||||
|
* respectively, and skip the rest.
|
||||||
|
*/
|
||||||
|
ellipse_range (ty0, &x0, &x1);
|
||||||
|
|
||||||
|
if (tx0 >= x0 && tx1 <= x1)
|
||||||
{
|
{
|
||||||
/* algorithm changed 7-18-04, because the previous one
|
ellipse_range (ty1, &x0, &x1);
|
||||||
* did not work well for eccentric ellipses. The new
|
|
||||||
* algorithm measures the distance to the ellipse in the
|
|
||||||
* X and Y directions, and uses trigonometry to
|
|
||||||
* approximate the distance to the ellipse as the
|
|
||||||
* distance to the hypotenuse of a right triangle whose
|
|
||||||
* legs are the X and Y distances. (WES)
|
|
||||||
*/
|
|
||||||
const gfloat yi = ABS (py + 0.5 - ellipse_center_y);
|
|
||||||
gfloat last_val = -1;
|
|
||||||
gint x_start = px;
|
|
||||||
gint cur_x;
|
|
||||||
|
|
||||||
for (cur_x = px; cur_x < (px + roi->width); cur_x++)
|
if (tx0 > x0 && tx1 <= x1)
|
||||||
{
|
{
|
||||||
gfloat xj;
|
fill1 (d, iter->length);
|
||||||
gfloat xdist;
|
|
||||||
gfloat ydist;
|
|
||||||
gfloat r;
|
|
||||||
gfloat dist;
|
|
||||||
gfloat val;
|
|
||||||
|
|
||||||
if (cur_x < x + w / 2)
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (tx1 < x0 || tx0 > x1)
|
||||||
|
{
|
||||||
|
ellipse_range (ty1, &x0, &x1);
|
||||||
|
|
||||||
|
if (tx1 < x0 || tx0 > x1)
|
||||||
|
{
|
||||||
|
if ((ty0 - cy) * (ty1 - cy) >= 0.0)
|
||||||
{
|
{
|
||||||
ellipse_center_x = x + a;
|
fill0 (d, iter->length);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ellipse_center_x = x + w - a;
|
|
||||||
}
|
|
||||||
|
|
||||||
xj = ABS (cur_x + 0.5 - ellipse_center_x);
|
continue;
|
||||||
|
|
||||||
if (yi < b)
|
|
||||||
xdist = xj - a * sqrt (1 - SQR (yi) / b_sqr);
|
|
||||||
else
|
|
||||||
xdist = 1000.0; /* anything large will work */
|
|
||||||
|
|
||||||
if (xj < a)
|
|
||||||
ydist = yi - b * sqrt (1 - SQR (xj) / a_sqr);
|
|
||||||
else
|
|
||||||
ydist = 1000.0; /* anything large will work */
|
|
||||||
|
|
||||||
r = hypot (xdist, ydist);
|
|
||||||
|
|
||||||
if (r < 0.001)
|
|
||||||
dist = 0.0;
|
|
||||||
else
|
|
||||||
dist = xdist * ydist / r; /* trig formula for distance to
|
|
||||||
* hypotenuse
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (xdist < 0.0)
|
|
||||||
dist *= -1;
|
|
||||||
|
|
||||||
if (dist < -0.5)
|
|
||||||
val = 1.0;
|
|
||||||
else if (dist < 0.5)
|
|
||||||
val = (1.0 - (dist + 0.5));
|
|
||||||
else
|
|
||||||
val = 0.0;
|
|
||||||
|
|
||||||
if (last_val != val)
|
|
||||||
{
|
|
||||||
if (last_val != -1)
|
|
||||||
gimp_gegl_mask_combine_span (data, op,
|
|
||||||
MAX (x_start - px, 0),
|
|
||||||
MIN (cur_x - px, roi->width),
|
|
||||||
last_val);
|
|
||||||
|
|
||||||
x_start = cur_x;
|
|
||||||
last_val = val;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* skip ahead if we are on the straight segment
|
|
||||||
* between rounded corners
|
|
||||||
*/
|
|
||||||
if (cur_x >= x + a && cur_x < x + w - a)
|
|
||||||
{
|
|
||||||
gimp_gegl_mask_combine_span (data, op,
|
|
||||||
MAX (x_start - px, 0),
|
|
||||||
MIN (cur_x - px, roi->width),
|
|
||||||
last_val);
|
|
||||||
|
|
||||||
x_start = cur_x;
|
|
||||||
cur_x = x + w - a;
|
|
||||||
last_val = val = 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Time to change center? */
|
|
||||||
if (cur_x >= x + w / 2)
|
|
||||||
{
|
|
||||||
ellipse_center_x = x + w - a;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
gimp_gegl_mask_combine_span (data, op,
|
for (y = roi->y; y < roi->y + roi->height; y++)
|
||||||
MAX (x_start - px, 0),
|
{
|
||||||
MIN (cur_x - px, roi->width),
|
gint a, b;
|
||||||
last_val);
|
|
||||||
|
if (antialias)
|
||||||
|
{
|
||||||
|
gdouble v = y_to_v (y + 0.5);
|
||||||
|
gdouble u0 = v_to_u (v - 0.5);
|
||||||
|
gdouble u1 = v_to_u (v + 0.5);
|
||||||
|
gint x;
|
||||||
|
|
||||||
|
a = floor (u_to_x_left (u0)) - roi->x;
|
||||||
|
a = CLAMP (a, 0, roi->width);
|
||||||
|
|
||||||
|
b = ceil (u_to_x_left (u1)) - roi->x;
|
||||||
|
b = CLAMP (b, a, roi->width);
|
||||||
|
|
||||||
|
d = fill0 (d, a);
|
||||||
|
|
||||||
|
for (x = roi->x + a; x < roi->x + b; x++)
|
||||||
|
d = set (d, pixel_value (x, y));
|
||||||
|
|
||||||
|
a = floor (u_to_x_right (u1)) - roi->x;
|
||||||
|
a = CLAMP (a, b, roi->width);
|
||||||
|
|
||||||
|
d = fill1 (d, a - b);
|
||||||
|
|
||||||
|
b = ceil (u_to_x_right (u0)) - roi->x;
|
||||||
|
b = CLAMP (b, a, roi->width);
|
||||||
|
|
||||||
|
for (x = roi->x + a; x < roi->x + b; x++)
|
||||||
|
d = set (d, pixel_value (x, y));
|
||||||
|
|
||||||
|
d = fill0 (d, roi->width - b);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ellipse_range (y + 0.5, &x0, &x1);
|
||||||
|
|
||||||
|
a = ceil (x0 - 0.5) - roi->x;
|
||||||
|
a = CLAMP (a, 0, roi->width);
|
||||||
|
|
||||||
|
b = floor (x1 + 0.5) - roi->x;
|
||||||
|
b = CLAMP (b, 0, roi->width);
|
||||||
|
|
||||||
|
d = fill0 (d, a);
|
||||||
|
d = fill1 (d, b - a);
|
||||||
|
d = fill0 (d, roi->width - b);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
@ -38,8 +38,8 @@ gboolean gimp_gegl_mask_combine_ellipse_rect (GeglBuffer *mask,
|
|||||||
gint y,
|
gint y,
|
||||||
gint w,
|
gint w,
|
||||||
gint h,
|
gint h,
|
||||||
gdouble a,
|
gdouble rx,
|
||||||
gdouble b,
|
gdouble ry,
|
||||||
gboolean antialias);
|
gboolean antialias);
|
||||||
gboolean gimp_gegl_mask_combine_buffer (GeglBuffer *mask,
|
gboolean gimp_gegl_mask_combine_buffer (GeglBuffer *mask,
|
||||||
GeglBuffer *add_on,
|
GeglBuffer *add_on,
|
||||||
|
Reference in New Issue
Block a user