PIKApp/app/operations/pikaoperationgradient.c

1288 lines
43 KiB
C

/* PIKA - Photo and Image Kooker Application
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
*
* Original copyright, applying to most contents (license remains unchanged):
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* Largely based on pikadrawable-gradient.c
*
* pikaoperationgradient.c
* Copyright (C) 2014 Michael Henning <drawoc@darkrefraction.com>
*
* 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 <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <cairo.h>
#include <gegl.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include "libpikacolor/pikacolor.h"
#include "libpikamath/pikamath.h"
#include "operations-types.h"
#include "core/pikagradient.h"
#include "pikaoperationgradient.h"
#define GRADIENT_CACHE_N_SUPERSAMPLES 4
#define GRADIENT_CACHE_MAX_SIZE ((1 << 20) / sizeof (PikaRGB))
enum
{
PROP_0,
PROP_CONTEXT,
PROP_GRADIENT,
PROP_START_X,
PROP_START_Y,
PROP_END_X,
PROP_END_Y,
PROP_GRADIENT_TYPE,
PROP_GRADIENT_REPEAT,
PROP_OFFSET,
PROP_GRADIENT_REVERSE,
PROP_GRADIENT_BLEND_COLOR_SPACE,
PROP_SUPERSAMPLE,
PROP_SUPERSAMPLE_DEPTH,
PROP_SUPERSAMPLE_THRESHOLD,
PROP_DITHER
};
typedef struct
{
PikaGradient *gradient;
gboolean reverse;
PikaGradientBlendColorSpace blend_color_space;
PikaRGB *gradient_cache;
gint gradient_cache_size;
PikaGradientSegment *last_seg;
gdouble offset;
gdouble sx, sy;
PikaGradientType gradient_type;
gdouble dist;
gdouble vec[2];
PikaRepeatMode repeat;
GeglSampler *dist_sampler;
} RenderBlendData;
typedef struct
{
gfloat *data;
GeglRectangle roi;
GRand *dither_rand;
} PutPixelData;
/* local function prototypes */
static void pika_operation_gradient_dispose (GObject *gobject);
static void pika_operation_gradient_finalize (GObject *gobject);
static void pika_operation_gradient_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static void pika_operation_gradient_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void pika_operation_gradient_prepare (GeglOperation *operation);
static GeglRectangle pika_operation_gradient_get_bounding_box (GeglOperation *operation);
static gdouble gradient_calc_conical_sym_factor (gdouble dist,
gdouble *axis,
gdouble offset,
gdouble x,
gdouble y);
static gdouble gradient_calc_conical_asym_factor (gdouble dist,
gdouble *axis,
gdouble offset,
gdouble x,
gdouble y);
static gdouble gradient_calc_square_factor (gdouble dist,
gdouble offset,
gdouble x,
gdouble y);
static gdouble gradient_calc_radial_factor (gdouble dist,
gdouble offset,
gdouble x,
gdouble y);
static gdouble gradient_calc_linear_factor (gdouble dist,
gdouble *vec,
gdouble offset,
gdouble x,
gdouble y);
static gdouble gradient_calc_bilinear_factor (gdouble dist,
gdouble *vec,
gdouble offset,
gdouble x,
gdouble y);
static gdouble gradient_calc_spiral_factor (gdouble dist,
gdouble *axis,
gdouble offset,
gdouble x,
gdouble y,
gboolean clockwise);
static gdouble gradient_calc_shapeburst_angular_factor (GeglSampler *dist_sampler,
gdouble offset,
gdouble x,
gdouble y);
static gdouble gradient_calc_shapeburst_spherical_factor (GeglSampler *dist_sampler,
gdouble offset,
gdouble x,
gdouble y);
static gdouble gradient_calc_shapeburst_dimpled_factor (GeglSampler *dist_sampler,
gdouble offset,
gdouble x,
gdouble y);
static void gradient_render_pixel (gdouble x,
gdouble y,
PikaRGB *color,
gpointer render_data);
static void gradient_put_pixel (gint x,
gint y,
PikaRGB *color,
gpointer put_pixel_data);
static void gradient_dither_pixel (PikaRGB *color,
GRand *dither_rand,
gfloat *dest);
static gboolean pika_operation_gradient_process (GeglOperation *operation,
GeglBuffer *input,
GeglBuffer *output,
const GeglRectangle *result,
gint level);
static void pika_operation_gradient_invalidate_cache (PikaOperationGradient *self);
static void pika_operation_gradient_validate_cache (PikaOperationGradient *self);
G_DEFINE_TYPE (PikaOperationGradient, pika_operation_gradient,
GEGL_TYPE_OPERATION_FILTER)
#define parent_class pika_operation_gradient_parent_class
static void
pika_operation_gradient_class_init (PikaOperationGradientClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GeglOperationClass *operation_class = GEGL_OPERATION_CLASS (klass);
GeglOperationFilterClass *filter_class = GEGL_OPERATION_FILTER_CLASS (klass);
object_class->dispose = pika_operation_gradient_dispose;
object_class->finalize = pika_operation_gradient_finalize;
object_class->set_property = pika_operation_gradient_set_property;
object_class->get_property = pika_operation_gradient_get_property;
operation_class->prepare = pika_operation_gradient_prepare;
operation_class->get_bounding_box = pika_operation_gradient_get_bounding_box;
filter_class->process = pika_operation_gradient_process;
gegl_operation_class_set_keys (operation_class,
"name", "pika:gradient",
"categories", "pika",
"description", "PIKA Gradient operation",
NULL);
g_object_class_install_property (object_class, PROP_CONTEXT,
g_param_spec_object ("context",
"Context",
"A PikaContext",
PIKA_TYPE_OBJECT,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_GRADIENT,
g_param_spec_object ("gradient",
"Gradient",
"A PikaGradient to render",
PIKA_TYPE_OBJECT,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_START_X,
g_param_spec_double ("start-x",
"Start X",
"X coordinate of the first point",
-G_MAXDOUBLE, G_MAXDOUBLE, 0,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_START_Y,
g_param_spec_double ("start-y",
"Start Y",
"Y coordinate of the first point",
-G_MAXDOUBLE, G_MAXDOUBLE, 0,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_END_X,
g_param_spec_double ("end-x",
"End X",
"X coordinate of the second point",
-G_MAXDOUBLE, G_MAXDOUBLE, 200,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_END_Y,
g_param_spec_double ("end-y",
"End Y",
"Y coordinate of the second point",
-G_MAXDOUBLE, G_MAXDOUBLE, 200,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_GRADIENT_TYPE,
g_param_spec_enum ("gradient-type",
"Gradient Type",
"The type of gradient to render",
PIKA_TYPE_GRADIENT_TYPE,
PIKA_GRADIENT_LINEAR,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_GRADIENT_REPEAT,
g_param_spec_enum ("gradient-repeat",
"Repeat mode",
"Repeat mode",
PIKA_TYPE_REPEAT_MODE,
PIKA_REPEAT_NONE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_OFFSET,
g_param_spec_double ("offset",
"Offset",
"Offset relates to the starting and ending coordinates "
"specified for the blend. This parameter is mode dependent.",
0, G_MAXDOUBLE, 0,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_GRADIENT_REVERSE,
g_param_spec_boolean ("gradient-reverse",
"Reverse",
"Reverse the gradient",
FALSE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_GRADIENT_BLEND_COLOR_SPACE,
g_param_spec_enum ("gradient-blend-color-space",
"Blend Color Space",
"Which color space to use when blending RGB gradient segments",
PIKA_TYPE_GRADIENT_BLEND_COLOR_SPACE,
PIKA_GRADIENT_BLEND_RGB_PERCEPTUAL,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_SUPERSAMPLE,
g_param_spec_boolean ("supersample",
"Supersample",
"Do adaptive supersampling",
FALSE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_SUPERSAMPLE_DEPTH,
g_param_spec_int ("supersample-depth",
"Max depth",
"Maximum recursion levels for supersampling",
1, 9, 3,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_SUPERSAMPLE_THRESHOLD,
g_param_spec_double ("supersample-threshold",
"Threshold",
"Supersampling threshold",
0, 4, 0.20,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_DITHER,
g_param_spec_boolean ("dither",
"Dither",
"Use dithering to reduce banding",
FALSE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
}
static void
pika_operation_gradient_init (PikaOperationGradient *self)
{
g_mutex_init (&self->gradient_cache_mutex);
}
static void
pika_operation_gradient_dispose (GObject *object)
{
PikaOperationGradient *self = PIKA_OPERATION_GRADIENT (object);
pika_operation_gradient_invalidate_cache (self);
g_clear_object (&self->gradient);
g_clear_object (&self->context);
G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
pika_operation_gradient_finalize (GObject *object)
{
PikaOperationGradient *self = PIKA_OPERATION_GRADIENT (object);
g_mutex_clear (&self->gradient_cache_mutex);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
pika_operation_gradient_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
PikaOperationGradient *self = PIKA_OPERATION_GRADIENT (object);
switch (property_id)
{
case PROP_CONTEXT:
g_value_set_object (value, self->context);
break;
case PROP_GRADIENT:
g_value_set_object (value, self->gradient);
break;
case PROP_START_X:
g_value_set_double (value, self->start_x);
break;
case PROP_START_Y:
g_value_set_double (value, self->start_y);
break;
case PROP_END_X:
g_value_set_double (value, self->end_x);
break;
case PROP_END_Y:
g_value_set_double (value, self->end_y);
break;
case PROP_GRADIENT_TYPE:
g_value_set_enum (value, self->gradient_type);
break;
case PROP_GRADIENT_REPEAT:
g_value_set_enum (value, self->gradient_repeat);
break;
case PROP_OFFSET:
g_value_set_double (value, self->offset);
break;
case PROP_GRADIENT_REVERSE:
g_value_set_boolean (value, self->gradient_reverse);
break;
case PROP_GRADIENT_BLEND_COLOR_SPACE:
g_value_set_enum (value, self->gradient_blend_color_space);
break;
case PROP_SUPERSAMPLE:
g_value_set_boolean (value, self->supersample);
break;
case PROP_SUPERSAMPLE_DEPTH:
g_value_set_int (value, self->supersample_depth);
break;
case PROP_SUPERSAMPLE_THRESHOLD:
g_value_set_double (value, self->supersample_threshold);
break;
case PROP_DITHER:
g_value_set_boolean (value, self->dither);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
pika_operation_gradient_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
PikaOperationGradient *self = PIKA_OPERATION_GRADIENT (object);
switch (property_id)
{
case PROP_CONTEXT:
if (self->context)
g_object_unref (self->context);
self->context = g_value_dup_object (value);
break;
case PROP_GRADIENT:
{
PikaGradient *gradient = g_value_get_object (value);
g_clear_object (&self->gradient);
if (gradient)
{
if (pika_gradient_has_fg_bg_segments (gradient))
self->gradient = pika_gradient_flatten (gradient, self->context);
else
self->gradient = g_object_ref (gradient);
}
pika_operation_gradient_invalidate_cache (self);
}
break;
case PROP_START_X:
self->start_x = g_value_get_double (value);
pika_operation_gradient_invalidate_cache (self);
break;
case PROP_START_Y:
self->start_y = g_value_get_double (value);
pika_operation_gradient_invalidate_cache (self);
break;
case PROP_END_X:
self->end_x = g_value_get_double (value);
pika_operation_gradient_invalidate_cache (self);
break;
case PROP_END_Y:
self->end_y = g_value_get_double (value);
pika_operation_gradient_invalidate_cache (self);
break;
case PROP_GRADIENT_TYPE:
self->gradient_type = g_value_get_enum (value);
break;
case PROP_GRADIENT_REPEAT:
self->gradient_repeat = g_value_get_enum (value);
break;
case PROP_OFFSET:
self->offset = g_value_get_double (value);
break;
case PROP_GRADIENT_REVERSE:
self->gradient_reverse = g_value_get_boolean (value);
pika_operation_gradient_invalidate_cache (self);
break;
case PROP_GRADIENT_BLEND_COLOR_SPACE:
self->gradient_blend_color_space = g_value_get_enum (value);
pika_operation_gradient_invalidate_cache (self);
break;
case PROP_SUPERSAMPLE:
self->supersample = g_value_get_boolean (value);
break;
case PROP_SUPERSAMPLE_DEPTH:
self->supersample_depth = g_value_get_int (value);
break;
case PROP_SUPERSAMPLE_THRESHOLD:
self->supersample_threshold = g_value_get_double (value);
break;
case PROP_DITHER:
self->dither = g_value_get_boolean (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
pika_operation_gradient_prepare (GeglOperation *operation)
{
gegl_operation_set_format (operation, "output", babl_format ("R'G'B'A float"));
}
static GeglRectangle
pika_operation_gradient_get_bounding_box (GeglOperation *operation)
{
return gegl_rectangle_infinite_plane ();
}
static gdouble
gradient_calc_conical_sym_factor (gdouble dist,
gdouble *axis,
gdouble offset,
gdouble x,
gdouble y)
{
if (dist == 0.0)
{
return 0.0;
}
else if ((x != 0) || (y != 0))
{
gdouble vec[2];
gdouble r;
gdouble rat;
/* Calculate offset from the start in pixels */
r = sqrt (SQR (x) + SQR (y));
vec[0] = x / r;
vec[1] = y / r;
rat = axis[0] * vec[0] + axis[1] * vec[1]; /* Dot product */
if (rat > 1.0)
rat = 1.0;
else if (rat < -1.0)
rat = -1.0;
/* This cool idea is courtesy Josh MacDonald,
* Ali Rahimi --- two more XCF losers. */
rat = acos (rat) / G_PI;
rat = pow (rat, (offset / 10.0) + 1.0);
return CLAMP (rat, 0.0, 1.0);
}
else
{
return 0.5;
}
}
static gdouble
gradient_calc_conical_asym_factor (gdouble dist,
gdouble *axis,
gdouble offset,
gdouble x,
gdouble y)
{
if (dist == 0.0)
{
return 0.0;
}
else if (x != 0 || y != 0)
{
gdouble ang0, ang1;
gdouble ang;
gdouble rat;
ang0 = atan2 (axis[0], axis[1]) + G_PI;
ang1 = atan2 (x, y) + G_PI;
ang = ang1 - ang0;
if (ang < 0.0)
ang += (2.0 * G_PI);
rat = ang / (2.0 * G_PI);
rat = pow (rat, (offset / 10.0) + 1.0);
return CLAMP (rat, 0.0, 1.0);
}
else
{
return 0.5; /* We are on middle point */
}
}
static gdouble
gradient_calc_square_factor (gdouble dist,
gdouble offset,
gdouble x,
gdouble y)
{
if (dist == 0.0)
{
return 0.0;
}
else
{
gdouble r;
gdouble rat;
/* Calculate offset from start as a value in [0, 1] */
offset = offset / 100.0;
r = MAX (fabs (x), fabs (y));
rat = r / dist;
if (rat < offset)
return 0.0;
else if (offset == 1.0)
return (rat >= 1.0) ? 1.0 : 0.0;
else
return (rat - offset) / (1.0 - offset);
}
}
static gdouble
gradient_calc_radial_factor (gdouble dist,
gdouble offset,
gdouble x,
gdouble y)
{
if (dist == 0.0)
{
return 0.0;
}
else
{
gdouble r;
gdouble rat;
/* Calculate radial offset from start as a value in [0, 1] */
offset = offset / 100.0;
r = sqrt (SQR (x) + SQR (y));
rat = r / dist;
if (rat < offset)
return 0.0;
else if (offset == 1.0)
return (rat >= 1.0) ? 1.0 : 0.0;
else
return (rat - offset) / (1.0 - offset);
}
}
static gdouble
gradient_calc_linear_factor (gdouble dist,
gdouble *vec,
gdouble offset,
gdouble x,
gdouble y)
{
if (dist == 0.0)
{
return 0.0;
}
else
{
gdouble r;
gdouble rat;
offset = offset / 100.0;
r = vec[0] * x + vec[1] * y;
rat = r / dist;
if (rat >= 0.0 && rat < offset)
return 0.0;
else if (offset == 1.0)
return (rat >= 1.0) ? 1.0 : 0.0;
else if (rat < 0.0)
return rat / (1.0 - offset);
else
return (rat - offset) / (1.0 - offset);
}
}
static gdouble
gradient_calc_bilinear_factor (gdouble dist,
gdouble *vec,
gdouble offset,
gdouble x,
gdouble y)
{
if (dist == 0.0)
{
return 0.0;
}
else
{
gdouble r;
gdouble rat;
/* Calculate linear offset from the start line outward */
offset = offset / 100.0;
r = vec[0] * x + vec[1] * y;
rat = r / dist;
if (fabs (rat) < offset)
return 0.0;
else if (offset == 1.0)
return (rat == 1.0) ? 1.0 : 0.0;
else
return (fabs (rat) - offset) / (1.0 - offset);
}
}
static gdouble
gradient_calc_spiral_factor (gdouble dist,
gdouble *axis,
gdouble offset,
gdouble x,
gdouble y,
gboolean clockwise)
{
if (dist == 0.0)
{
return 0.0;
}
else if (x != 0.0 || y != 0.0)
{
gdouble ang0, ang1;
gdouble ang;
double r;
offset = offset / 100.0;
ang0 = atan2 (axis[0], axis[1]) + G_PI;
ang1 = atan2 (x, y) + G_PI;
if (clockwise)
ang = ang1 - ang0;
else
ang = ang0 - ang1;
if (ang < 0.0)
ang += (2.0 * G_PI);
r = sqrt (SQR (x) + SQR (y)) / dist;
return fmod (ang / (2.0 * G_PI) + r + offset, 1.0);
}
else
{
return 0.5 ; /* We are on the middle point */
}
}
static gdouble
gradient_calc_shapeburst_angular_factor (GeglSampler *dist_sampler,
gdouble offset,
gdouble x,
gdouble y)
{
gfloat value;
offset = offset / 100.0;
gegl_sampler_get (dist_sampler, x, y, NULL, &value, GEGL_ABYSS_NONE);
value = 1.0 - value;
if (value < offset)
value = 0.0;
else if (offset == 1.0)
value = (value >= 1.0) ? 1.0 : 0.0;
else
value = (value - offset) / (1.0 - offset);
return value;
}
static gdouble
gradient_calc_shapeburst_spherical_factor (GeglSampler *dist_sampler,
gdouble offset,
gdouble x,
gdouble y)
{
gfloat value;
offset = 1.0 - offset / 100.0;
gegl_sampler_get (dist_sampler, x, y, NULL, &value, GEGL_ABYSS_NONE);
if (value > offset)
value = 1.0;
else if (offset == 0.0)
value = (value <= 0.0) ? 0.0 : 1.0;
else
value = value / offset;
value = 1.0 - sin (0.5 * G_PI * value);
return value;
}
static gdouble
gradient_calc_shapeburst_dimpled_factor (GeglSampler *dist_sampler,
gdouble offset,
gdouble x,
gdouble y)
{
gfloat value;
offset = 1.0 - offset / 100.0;
gegl_sampler_get (dist_sampler, x, y, NULL, &value, GEGL_ABYSS_NONE);
if (value > offset)
value = 1.0;
else if (offset == 0.0)
value = (value <= 0.0) ? 0.0 : 1.0;
else
value = value / offset;
value = cos (0.5 * G_PI * value);
return value;
}
static void
gradient_render_pixel (gdouble x,
gdouble y,
PikaRGB *color,
gpointer render_data)
{
RenderBlendData *rbd = render_data;
gdouble factor;
/* we want to calculate the color at the pixel's center */
x += 0.5;
y += 0.5;
/* Calculate blending factor */
switch (rbd->gradient_type)
{
case PIKA_GRADIENT_LINEAR:
factor = gradient_calc_linear_factor (rbd->dist,
rbd->vec, rbd->offset,
x - rbd->sx, y - rbd->sy);
break;
case PIKA_GRADIENT_BILINEAR:
factor = gradient_calc_bilinear_factor (rbd->dist,
rbd->vec, rbd->offset,
x - rbd->sx, y - rbd->sy);
break;
case PIKA_GRADIENT_RADIAL:
factor = gradient_calc_radial_factor (rbd->dist,
rbd->offset,
x - rbd->sx, y - rbd->sy);
break;
case PIKA_GRADIENT_SQUARE:
factor = gradient_calc_square_factor (rbd->dist, rbd->offset,
x - rbd->sx, y - rbd->sy);
break;
case PIKA_GRADIENT_CONICAL_SYMMETRIC:
factor = gradient_calc_conical_sym_factor (rbd->dist,
rbd->vec, rbd->offset,
x - rbd->sx, y - rbd->sy);
break;
case PIKA_GRADIENT_CONICAL_ASYMMETRIC:
factor = gradient_calc_conical_asym_factor (rbd->dist,
rbd->vec, rbd->offset,
x - rbd->sx, y - rbd->sy);
break;
case PIKA_GRADIENT_SHAPEBURST_ANGULAR:
factor = gradient_calc_shapeburst_angular_factor (rbd->dist_sampler,
rbd->offset,
x, y);
break;
case PIKA_GRADIENT_SHAPEBURST_SPHERICAL:
factor = gradient_calc_shapeburst_spherical_factor (rbd->dist_sampler,
rbd->offset,
x, y);
break;
case PIKA_GRADIENT_SHAPEBURST_DIMPLED:
factor = gradient_calc_shapeburst_dimpled_factor (rbd->dist_sampler,
rbd->offset,
x, y);
break;
case PIKA_GRADIENT_SPIRAL_CLOCKWISE:
factor = gradient_calc_spiral_factor (rbd->dist,
rbd->vec, rbd->offset,
x - rbd->sx, y - rbd->sy, TRUE);
break;
case PIKA_GRADIENT_SPIRAL_ANTICLOCKWISE:
factor = gradient_calc_spiral_factor (rbd->dist,
rbd->vec, rbd->offset,
x - rbd->sx, y - rbd->sy, FALSE);
break;
default:
g_return_if_reached ();
break;
}
/* Adjust for repeat */
switch (rbd->repeat)
{
case PIKA_REPEAT_NONE:
break;
case PIKA_REPEAT_SAWTOOTH:
factor = factor - floor (factor);
break;
case PIKA_REPEAT_TRIANGULAR:
{
guint ifactor;
if (factor < 0.0)
factor = -factor;
ifactor = (guint) factor;
factor = factor - floor (factor);
if (ifactor & 1)
factor = 1.0 - factor;
}
break;
case PIKA_REPEAT_TRUNCATE:
if (factor < 0.0 || factor > 1.0)
{
pika_rgba_set (color, 0.0, 0.0, 0.0, 0.0);
return;
}
break;
}
/* Blend the colors */
if (rbd->gradient_cache)
{
factor = CLAMP (factor, 0.0, 1.0);
*color =
rbd->gradient_cache[ROUND (factor * (rbd->gradient_cache_size - 1))];
}
else
{
rbd->last_seg = pika_gradient_get_color_at (rbd->gradient, NULL,
rbd->last_seg, factor,
rbd->reverse,
rbd->blend_color_space,
color);
}
}
static void
gradient_put_pixel (gint x,
gint y,
PikaRGB *color,
gpointer put_pixel_data)
{
PutPixelData *ppd = put_pixel_data;
const gint index = (y - ppd->roi.y) * ppd->roi.width + (x - ppd->roi.x);
gfloat *dest = ppd->data + 4 * index;
if (ppd->dither_rand)
{
gradient_dither_pixel (color, ppd->dither_rand, dest);
dest += 4;
}
else
{
*dest++ = color->r;
*dest++ = color->g;
*dest++ = color->b;
*dest++ = color->a;
}
}
static void
gradient_dither_pixel (PikaRGB *color,
GRand *dither_rand,
gfloat *dest)
{
gfloat r, g, b, a;
guint i;
i = g_rand_int (dither_rand);
r = color->r + (gdouble) (i & 0xff) / 256.0 / 256.0 - 0.5 / 256.0; i >>= 8;
g = color->g + (gdouble) (i & 0xff) / 256.0 / 256.0 - 0.5 / 256.0; i >>= 8;
b = color->b + (gdouble) (i & 0xff) / 256.0 / 256.0 - 0.5 / 256.0; i >>= 8;
if (color->a > 0.0 && color->a < 1.0)
a = color->a + (gdouble) (i & 0xff) / 256.0 / 256.0 - 0.5 / 256.0;
else
a = color->a;
*dest++ = CLAMP (r, 0.0, 1.0);
*dest++ = CLAMP (g, 0.0, 1.0);
*dest++ = CLAMP (b, 0.0, 1.0);
*dest++ = CLAMP (a, 0.0, 1.0);
}
static gboolean
pika_operation_gradient_process (GeglOperation *operation,
GeglBuffer *input,
GeglBuffer *output,
const GeglRectangle *result,
gint level)
{
PikaOperationGradient *self = PIKA_OPERATION_GRADIENT (operation);
const gdouble sx = self->start_x;
const gdouble sy = self->start_y;
const gdouble ex = self->end_x;
const gdouble ey = self->end_y;
RenderBlendData rbd = { 0, };
GeglBufferIterator *iter;
GeglRectangle *roi;
GRand *dither_rand = NULL;
if (! self->gradient)
return TRUE;
pika_operation_gradient_validate_cache (self);
rbd.gradient = self->gradient;
rbd.reverse = self->gradient_reverse;
rbd.blend_color_space = self->gradient_blend_color_space;
rbd.gradient_cache = self->gradient_cache;
rbd.gradient_cache_size = self->gradient_cache_size;
/* Calculate type-specific parameters */
switch (self->gradient_type)
{
case PIKA_GRADIENT_RADIAL:
rbd.dist = sqrt (SQR (ex - sx) + SQR (ey - sy));
break;
case PIKA_GRADIENT_SQUARE:
rbd.dist = MAX (fabs (ex - sx), fabs (ey - sy));
break;
case PIKA_GRADIENT_CONICAL_SYMMETRIC:
case PIKA_GRADIENT_CONICAL_ASYMMETRIC:
case PIKA_GRADIENT_SPIRAL_CLOCKWISE:
case PIKA_GRADIENT_SPIRAL_ANTICLOCKWISE:
case PIKA_GRADIENT_LINEAR:
case PIKA_GRADIENT_BILINEAR:
rbd.dist = sqrt (SQR (ex - sx) + SQR (ey - sy));
if (rbd.dist > 0.0)
{
rbd.vec[0] = (ex - sx) / rbd.dist;
rbd.vec[1] = (ey - sy) / rbd.dist;
}
break;
case PIKA_GRADIENT_SHAPEBURST_ANGULAR:
case PIKA_GRADIENT_SHAPEBURST_SPHERICAL:
case PIKA_GRADIENT_SHAPEBURST_DIMPLED:
rbd.dist = sqrt (SQR (ex - sx) + SQR (ey - sy));
rbd.dist_sampler = gegl_buffer_sampler_new_at_level (
input, babl_format ("Y float"), GEGL_SAMPLER_NEAREST, level);
break;
default:
g_return_val_if_reached (FALSE);
break;
}
/* Initialize render data */
rbd.offset = self->offset;
rbd.sx = self->start_x;
rbd.sy = self->start_y;
rbd.gradient_type = self->gradient_type;
rbd.repeat = self->gradient_repeat;
/* Render the gradient! */
iter = gegl_buffer_iterator_new (output, result, 0,
babl_format ("R'G'B'A float"),
GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE, 1);
roi = &iter->items[0].roi;
if (self->dither)
dither_rand = g_rand_new ();
if (self->supersample)
{
PutPixelData ppd;
ppd.dither_rand = dither_rand;
while (gegl_buffer_iterator_next (iter))
{
ppd.data = iter->items[0].data;
ppd.roi = *roi;
pika_adaptive_supersample_area (roi->x, roi->y,
roi->x + roi->width - 1,
roi->y + roi->height - 1,
self->supersample_depth,
self->supersample_threshold,
gradient_render_pixel, &rbd,
gradient_put_pixel, &ppd,
NULL,
NULL);
}
}
else
{
while (gegl_buffer_iterator_next (iter))
{
gfloat *dest = iter->items[0].data;
gint endx = roi->x + roi->width;
gint endy = roi->y + roi->height;
gint x, y;
if (dither_rand)
{
for (y = roi->y; y < endy; y++)
for (x = roi->x; x < endx; x++)
{
PikaRGB color = { 0.0, 0.0, 0.0, 1.0 };
gradient_render_pixel (x, y, &color, &rbd);
gradient_dither_pixel (&color, dither_rand, dest);
dest += 4;
}
}
else
{
for (y = roi->y; y < endy; y++)
for (x = roi->x; x < endx; x++)
{
PikaRGB color = { 0.0, 0.0, 0.0, 1.0 };
gradient_render_pixel (x, y, &color, &rbd);
*dest++ = color.r;
*dest++ = color.g;
*dest++ = color.b;
*dest++ = color.a;
}
}
}
}
if (self->dither)
g_rand_free (dither_rand);
g_clear_object (&rbd.dist_sampler);
return TRUE;
}
static void
pika_operation_gradient_invalidate_cache (PikaOperationGradient *self)
{
g_clear_pointer (&self->gradient_cache, g_free);
}
static void
pika_operation_gradient_validate_cache (PikaOperationGradient *self)
{
PikaGradientSegment *last_seg = NULL;
gint cache_size;
gint i;
if (! self->gradient)
return;
g_mutex_lock (&self->gradient_cache_mutex);
if (self->gradient_cache)
{
g_mutex_unlock (&self->gradient_cache_mutex);
return;
}
cache_size = ceil (hypot (self->start_x - self->end_x,
self->start_y - self->end_y)) *
GRADIENT_CACHE_N_SUPERSAMPLES;
/* have at least two values in the cache */
cache_size = MAX (cache_size, 2);
/* don't use a cache if its necessary size is too big */
if (cache_size > GRADIENT_CACHE_MAX_SIZE)
{
g_mutex_unlock (&self->gradient_cache_mutex);
return;
}
self->gradient_cache = g_new0 (PikaRGB, cache_size);
self->gradient_cache_size = cache_size;
for (i = 0; i < self->gradient_cache_size; i++)
{
gdouble factor = (gdouble) i / (gdouble) (self->gradient_cache_size - 1);
last_seg = pika_gradient_get_color_at (self->gradient, NULL, last_seg,
factor,
self->gradient_reverse,
self->gradient_blend_color_space,
self->gradient_cache + i);
}
g_mutex_unlock (&self->gradient_cache_mutex);
}