1288 lines
43 KiB
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);
|
|
}
|