gimp/app/core/gimpgradient.c

2415 lines
67 KiB
C

/* 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 <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <string.h>
#include <cairo.h>
#include <gegl.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include "libgimpcolor/gimpcolor.h"
#include "libgimpmath/gimpmath.h"
#include "core-types.h"
#include "gimpcontext.h"
#include "gimpgradient.h"
#include "gimpgradient-load.h"
#include "gimpgradient-save.h"
#include "gimptagged.h"
#include "gimptempbuf.h"
#define EPSILON 1e-10
static void gimp_gradient_tagged_iface_init (GimpTaggedInterface *iface);
static void gimp_gradient_finalize (GObject *object);
static gint64 gimp_gradient_get_memsize (GimpObject *object,
gint64 *gui_size);
static void gimp_gradient_get_preview_size (GimpViewable *viewable,
gint size,
gboolean popup,
gboolean dot_for_dot,
gint *width,
gint *height);
static gboolean gimp_gradient_get_popup_size (GimpViewable *viewable,
gint width,
gint height,
gboolean dot_for_dot,
gint *popup_width,
gint *popup_height);
static GimpTempBuf * gimp_gradient_get_new_preview (GimpViewable *viewable,
GimpContext *context,
gint width,
gint height);
static const gchar * gimp_gradient_get_extension (GimpData *data);
static void gimp_gradient_copy (GimpData *data,
GimpData *src_data);
static gint gimp_gradient_compare (GimpData *data1,
GimpData *data2);
static gchar * gimp_gradient_get_checksum (GimpTagged *tagged);
static inline GimpGradientSegment *
gimp_gradient_get_segment_at_internal (GimpGradient *gradient,
GimpGradientSegment *seg,
gdouble pos);
static GeglColor * gimp_gradient_get_flat_color (GimpContext *context,
GeglColor *color,
GimpGradientColor color_type);
static inline gdouble gimp_gradient_calc_linear_factor (gdouble middle,
gdouble pos);
static inline gdouble gimp_gradient_calc_curved_factor (gdouble middle,
gdouble pos);
static inline gdouble gimp_gradient_calc_sine_factor (gdouble middle,
gdouble pos);
static inline gdouble gimp_gradient_calc_sphere_increasing_factor (gdouble middle,
gdouble pos);
static inline gdouble gimp_gradient_calc_sphere_decreasing_factor (gdouble middle,
gdouble pos);
static inline gdouble gimp_gradient_calc_step_factor (gdouble middle,
gdouble pos);
G_DEFINE_TYPE_WITH_CODE (GimpGradient, gimp_gradient, GIMP_TYPE_DATA,
G_IMPLEMENT_INTERFACE (GIMP_TYPE_TAGGED,
gimp_gradient_tagged_iface_init))
#define parent_class gimp_gradient_parent_class
static const Babl *fish_srgb_to_linear_rgb = NULL;
static const Babl *fish_linear_rgb_to_srgb = NULL;
static const Babl *fish_srgb_to_cie_lab = NULL;
static const Babl *fish_cie_lab_to_srgb = NULL;
static void
gimp_gradient_class_init (GimpGradientClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GimpObjectClass *gimp_object_class = GIMP_OBJECT_CLASS (klass);
GimpViewableClass *viewable_class = GIMP_VIEWABLE_CLASS (klass);
GimpDataClass *data_class = GIMP_DATA_CLASS (klass);
object_class->finalize = gimp_gradient_finalize;
gimp_object_class->get_memsize = gimp_gradient_get_memsize;
viewable_class->default_icon_name = "gimp-tool-gradient";
viewable_class->get_preview_size = gimp_gradient_get_preview_size;
viewable_class->get_popup_size = gimp_gradient_get_popup_size;
viewable_class->get_new_preview = gimp_gradient_get_new_preview;
data_class->save = gimp_gradient_save;
data_class->get_extension = gimp_gradient_get_extension;
data_class->copy = gimp_gradient_copy;
data_class->compare = gimp_gradient_compare;
fish_srgb_to_linear_rgb = babl_fish (babl_format ("R'G'B' double"),
babl_format ("RGB double"));
fish_linear_rgb_to_srgb = babl_fish (babl_format ("RGB double"),
babl_format ("R'G'B' double"));
fish_srgb_to_cie_lab = babl_fish (babl_format ("R'G'B' double"),
babl_format ("CIE Lab float"));
fish_cie_lab_to_srgb = babl_fish (babl_format ("CIE Lab float"),
babl_format ("R'G'B' double"));
}
static void
gimp_gradient_tagged_iface_init (GimpTaggedInterface *iface)
{
iface->get_checksum = gimp_gradient_get_checksum;
}
static void
gimp_gradient_init (GimpGradient *gradient)
{
gradient->segments = NULL;
}
static void
gimp_gradient_finalize (GObject *object)
{
GimpGradient *gradient = GIMP_GRADIENT (object);
if (gradient->segments)
{
gimp_gradient_segments_free (gradient->segments);
gradient->segments = NULL;
}
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static gint64
gimp_gradient_get_memsize (GimpObject *object,
gint64 *gui_size)
{
GimpGradient *gradient = GIMP_GRADIENT (object);
GimpGradientSegment *segment;
gint64 memsize = 0;
for (segment = gradient->segments; segment; segment = segment->next)
memsize += sizeof (GimpGradientSegment);
return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
gui_size);
}
static void
gimp_gradient_get_preview_size (GimpViewable *viewable,
gint size,
gboolean popup,
gboolean dot_for_dot,
gint *width,
gint *height)
{
*width = size;
*height = 1 + size / 2;
}
static gboolean
gimp_gradient_get_popup_size (GimpViewable *viewable,
gint width,
gint height,
gboolean dot_for_dot,
gint *popup_width,
gint *popup_height)
{
if (width < 128 || height < 32)
{
*popup_width = 128;
*popup_height = 32;
return TRUE;
}
return FALSE;
}
static GimpTempBuf *
gimp_gradient_get_new_preview (GimpViewable *viewable,
GimpContext *context,
gint width,
gint height)
{
GimpGradient *gradient = GIMP_GRADIENT (viewable);
GimpGradientSegment *seg = NULL;
GimpTempBuf *temp_buf;
guchar *buf;
guchar *p;
guchar *row;
gint x, y;
gdouble dx, cur_x;
dx = 1.0 / (width - 1);
cur_x = 0.0;
p = row = g_malloc (width * 4);
/* Create lines to fill the image */
for (x = 0; x < width; x++)
{
GeglColor *color = NULL;
seg = gimp_gradient_get_color_at (gradient, context, seg, cur_x,
FALSE,
GIMP_GRADIENT_BLEND_RGB_PERCEPTUAL,
&color);
gegl_color_get_pixel (color, babl_format ("R'G'B'A u8"), p);
p += 4;
cur_x += dx;
g_object_unref (color);
}
temp_buf = gimp_temp_buf_new (width, height, babl_format ("R'G'B'A u8"));
buf = gimp_temp_buf_get_data (temp_buf);
for (y = 0; y < height; y++)
memcpy (buf + (width * y * 4), row, width * 4);
g_free (row);
return temp_buf;
}
static void
gimp_gradient_copy (GimpData *data,
GimpData *src_data)
{
GimpGradient *gradient = GIMP_GRADIENT (data);
GimpGradient *src_gradient = GIMP_GRADIENT (src_data);
GimpGradientSegment *head, *prev, *cur, *orig;
if (gradient->segments)
{
gimp_gradient_segments_free (gradient->segments);
gradient->segments = NULL;
}
prev = NULL;
orig = src_gradient->segments;
head = NULL;
while (orig)
{
cur = gimp_gradient_segment_new ();
g_clear_object (&cur->left_color);
g_clear_object (&cur->right_color);
*cur = *orig; /* Copy everything */
cur->prev = prev;
cur->next = NULL;
cur->left_color = gegl_color_duplicate (orig->left_color);
cur->right_color = gegl_color_duplicate (orig->right_color);
if (prev)
prev->next = cur;
else
head = cur; /* Remember head */
prev = cur;
orig = orig->next;
}
gradient->segments = head;
gimp_data_dirty (GIMP_DATA (gradient));
}
static gint
gimp_gradient_compare (GimpData *data1,
GimpData *data2)
{
gboolean is_custom1;
gboolean is_custom2;
/* check whether data1 and data2 are the custom gradient, which is the only
* writable internal gradient.
*/
is_custom1 = gimp_data_is_internal (data1) && gimp_data_is_writable (data1);
is_custom2 = gimp_data_is_internal (data2) && gimp_data_is_writable (data2);
/* order the custom gradient before all the other gradients; use the default
* ordering for the rest.
*/
if (is_custom1)
{
if (is_custom2)
return 0;
else
return -1;
}
else if (is_custom2)
{
return +1;
}
else
return GIMP_DATA_CLASS (parent_class)->compare (data1, data2);
}
static gchar *
gimp_gradient_get_checksum (GimpTagged *tagged)
{
GimpGradient *gradient = GIMP_GRADIENT (tagged);
gchar *checksum_string = NULL;
if (gradient->segments)
{
GChecksum *checksum = g_checksum_new (G_CHECKSUM_MD5);
GimpGradientSegment *segment = gradient->segments;
guchar data[40];
while (segment)
{
const Babl *format;
gsize length;
g_checksum_update (checksum,
(const guchar *) &segment->left,
sizeof (segment->left));
g_checksum_update (checksum,
(const guchar *) &segment->middle,
sizeof (segment->middle));
g_checksum_update (checksum,
(const guchar *) &segment->right,
sizeof (segment->right));
g_checksum_update (checksum,
(const guchar *) &segment->left_color_type,
sizeof (segment->left_color_type));
format = gegl_color_get_format (segment->left_color);
gegl_color_get_pixel (segment->left_color, format, data);
length = babl_format_get_bytes_per_pixel (format);
g_checksum_update (checksum, data, (gssize) length);
g_checksum_update (checksum, (const guchar *) babl_get_name (format),
strlen (babl_get_name (format)));
g_checksum_update (checksum,
(const guchar *) &segment->right_color_type,
sizeof (segment->right_color_type));
format = gegl_color_get_format (segment->right_color);
gegl_color_get_pixel (segment->right_color, format, data);
length = babl_format_get_bytes_per_pixel (format);
g_checksum_update (checksum, data, (gssize) length);
g_checksum_update (checksum, (const guchar *) babl_get_name (format),
strlen (babl_get_name (format)));
g_checksum_update (checksum,
(const guchar *) &segment->type,
sizeof (segment->type));
g_checksum_update (checksum,
(const guchar *) &segment->color,
sizeof (segment->color));
segment = segment->next;
}
checksum_string = g_strdup (g_checksum_get_string (checksum));
g_checksum_free (checksum);
}
return checksum_string;
}
/* public functions */
GimpData *
gimp_gradient_new (GimpContext *context,
const gchar *name)
{
GimpGradient *gradient;
g_return_val_if_fail (name != NULL, NULL);
g_return_val_if_fail (*name != '\0', NULL);
gradient = g_object_new (GIMP_TYPE_GRADIENT,
"name", name,
NULL);
gradient->segments = gimp_gradient_segment_new ();
return GIMP_DATA (gradient);
}
GimpData *
gimp_gradient_get_standard (GimpContext *context)
{
static GimpData *standard_gradient = NULL;
if (! standard_gradient)
{
g_set_weak_pointer (&standard_gradient,
gimp_gradient_new (context, "Standard"));
gimp_data_clean (standard_gradient);
gimp_data_make_internal (standard_gradient, "gimp-gradient-standard");
}
return standard_gradient;
}
static const gchar *
gimp_gradient_get_extension (GimpData *data)
{
return GIMP_GRADIENT_FILE_EXTENSION;
}
/**
* gimp_gradient_get_color_at:
* @gradient: a gradient
* @context: a context
* @seg: (nullable): a segment to seed the search with (or %NULL)
* @pos: position in the gradient (between 0.0 and 1.0)
* @reverse: when %TRUE, use the reversed gradient
* @blend_color_space: color space to use for blending RGB segments
* @color: returns a newly allocated color
*
* If you are iterating over an gradient, you should pass the the
* return value from the last call for @seg.
*
* Returns: the gradient segment the color is from
**/
GimpGradientSegment *
gimp_gradient_get_color_at (GimpGradient *gradient,
GimpContext *context,
GimpGradientSegment *seg,
gdouble pos,
gboolean reverse,
GimpGradientBlendColorSpace blend_color_space,
GeglColor **color)
{
gdouble factor = 0.0;
gdouble seg_len;
gdouble middle;
GeglColor *left_color;
GeglColor *right_color;
gdouble left_alpha;
gdouble right_alpha;
/* type-check disabled to improve speed */
/* g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), NULL); */
g_return_val_if_fail (color != NULL && *color == NULL, NULL);
*color = gegl_color_new (NULL);
pos = CLAMP (pos, 0.0, 1.0);
if (reverse)
pos = 1.0 - pos;
seg = gimp_gradient_get_segment_at_internal (gradient, seg, pos);
seg_len = seg->right - seg->left;
if (seg_len < EPSILON)
{
middle = 0.5;
pos = 0.5;
}
else
{
middle = (seg->middle - seg->left) / seg_len;
pos = (pos - seg->left) / seg_len;
}
switch (seg->type)
{
case GIMP_GRADIENT_SEGMENT_LINEAR:
factor = gimp_gradient_calc_linear_factor (middle, pos);
break;
case GIMP_GRADIENT_SEGMENT_CURVED:
factor = gimp_gradient_calc_curved_factor (middle, pos);
break;
case GIMP_GRADIENT_SEGMENT_SINE:
factor = gimp_gradient_calc_sine_factor (middle, pos);
break;
case GIMP_GRADIENT_SEGMENT_SPHERE_INCREASING:
factor = gimp_gradient_calc_sphere_increasing_factor (middle, pos);
break;
case GIMP_GRADIENT_SEGMENT_SPHERE_DECREASING:
factor = gimp_gradient_calc_sphere_decreasing_factor (middle, pos);
break;
case GIMP_GRADIENT_SEGMENT_STEP:
factor = gimp_gradient_calc_step_factor (middle, pos);
break;
default:
g_warning ("%s: Unknown gradient type %d.", G_STRFUNC, seg->type);
break;
}
/* Get left/right colors */
if (context)
{
left_color = gimp_gradient_segment_get_left_flat_color (gradient, context, seg);
right_color = gimp_gradient_segment_get_right_flat_color (gradient, context, seg);
}
else
{
left_color = g_object_ref (seg->left_color);
right_color = g_object_ref (seg->right_color);
}
/* Calculate color components */
if (seg->color == GIMP_GRADIENT_SEGMENT_RGB)
{
gfloat float_components[3];
gdouble left_components[3];
gdouble right_components[3];
gdouble ret_components[3];
switch (blend_color_space)
{
case GIMP_GRADIENT_BLEND_CIE_LAB:
gegl_color_get_pixel (left_color, babl_format ("CIE Lab float"), float_components);
for (gint i = 0; i < 3; i++)
left_components[i] = float_components[i];
gegl_color_get_pixel (right_color, babl_format ("CIE Lab float"), float_components);
for (gint i = 0; i < 3; i++)
right_components[i] = float_components[i];
break;
case GIMP_GRADIENT_BLEND_RGB_LINEAR:
gegl_color_get_pixel (left_color, babl_format ("RGB double"), left_components);
gegl_color_get_pixel (right_color, babl_format ("RGB double"), right_components);
case GIMP_GRADIENT_BLEND_RGB_PERCEPTUAL:
/* TODO: shouldn't this format use the color space of the target
* image? Will it change computation? */
gegl_color_get_pixel (left_color, babl_format ("R'G'B' double"), left_components);
gegl_color_get_pixel (right_color, babl_format ("R'G'B' double"), right_components);
break;
}
ret_components[0] = left_components[0] + (right_components[0] - left_components[0]) * factor;
ret_components[1] = left_components[1] + (right_components[1] - left_components[1]) * factor;
ret_components[2] = left_components[2] + (right_components[2] - left_components[2]) * factor;
switch (blend_color_space)
{
case GIMP_GRADIENT_BLEND_CIE_LAB:
for (gint i = 0; i < 3; i++)
float_components[i] = (gfloat) ret_components[i];
gegl_color_set_pixel (*color, babl_format ("CIE Lab float"), float_components);
break;
case GIMP_GRADIENT_BLEND_RGB_LINEAR:
gegl_color_set_pixel (*color, babl_format ("RGB double"), ret_components);
break;
case GIMP_GRADIENT_BLEND_RGB_PERCEPTUAL:
gegl_color_set_pixel (*color, babl_format ("R'G'B' double"), ret_components);
break;
}
}
else
{
gfloat left_hsv[3];
gfloat right_hsv[3];
gegl_color_get_pixel (left_color, babl_format ("HSV float"), left_hsv);
gegl_color_get_pixel (right_color, babl_format ("HSV float"), right_hsv);
left_hsv[1] = left_hsv[1] + (right_hsv[1] - left_hsv[1]) * factor;
left_hsv[2] = left_hsv[2] + (right_hsv[2] - left_hsv[2]) * factor;
switch (seg->color)
{
case GIMP_GRADIENT_SEGMENT_HSV_CCW:
if (left_hsv[0] < right_hsv[0])
{
left_hsv[0] += (right_hsv[0] - left_hsv[0]) * factor;
}
else
{
left_hsv[0] += (1.0 - (left_hsv[0] - right_hsv[0])) * factor;
if (left_hsv[0] > 1.0)
left_hsv[0] -= 1.0;
}
break;
case GIMP_GRADIENT_SEGMENT_HSV_CW:
if (right_hsv[0] < left_hsv[0])
{
left_hsv[0] -= (left_hsv[0] - right_hsv[0]) * factor;
}
else
{
left_hsv[0] -= (1.0 - (right_hsv[0] - left_hsv[0])) * factor;
if (left_hsv[0] < 0.0)
left_hsv[0] += 1.0;
}
break;
default:
g_warning ("%s: Unknown coloring mode %d",
G_STRFUNC, (gint) seg->color);
break;
}
gegl_color_set_pixel (*color, babl_format ("HSV float"), left_hsv);
}
/* Calculate alpha */
gegl_color_get_rgba (left_color, NULL, NULL, NULL, &left_alpha);
gegl_color_get_rgba (right_color, NULL, NULL, NULL, &right_alpha);
gimp_color_set_alpha (*color, left_alpha + (right_alpha - left_alpha) * factor);
g_object_unref (left_color);
g_object_unref (right_color);
return seg;
}
GimpGradientSegment *
gimp_gradient_get_segment_at (GimpGradient *gradient,
gdouble pos)
{
g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), NULL);
return gimp_gradient_get_segment_at_internal (gradient, NULL, pos);
}
gboolean
gimp_gradient_has_fg_bg_segments (GimpGradient *gradient)
{
GimpGradientSegment *segment;
g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), FALSE);
for (segment = gradient->segments; segment; segment = segment->next)
if (segment->left_color_type != GIMP_GRADIENT_COLOR_FIXED ||
segment->right_color_type != GIMP_GRADIENT_COLOR_FIXED)
return TRUE;
return FALSE;
}
void
gimp_gradient_split_at (GimpGradient *gradient,
GimpContext *context,
GimpGradientSegment *seg,
gdouble pos,
GimpGradientBlendColorSpace blend_color_space,
GimpGradientSegment **newl,
GimpGradientSegment **newr)
{
GeglColor *color = NULL;
GimpGradientSegment *newseg;
g_return_if_fail (GIMP_IS_GRADIENT (gradient));
g_return_if_fail (GIMP_IS_CONTEXT (context));
gimp_data_freeze (GIMP_DATA (gradient));
pos = CLAMP (pos, 0.0, 1.0);
seg = gimp_gradient_get_segment_at_internal (gradient, seg, pos);
/* Get color at pos */
gimp_gradient_get_color_at (gradient, context, seg, pos,
FALSE, blend_color_space, &color);
/* Create a new segment and insert it in the list */
newseg = gimp_gradient_segment_new ();
newseg->prev = seg;
newseg->next = seg->next;
seg->next = newseg;
if (newseg->next)
newseg->next->prev = newseg;
/* Set coordinates of new segment */
newseg->left = pos;
newseg->right = seg->right;
newseg->middle = (newseg->left + newseg->right) / 2.0;
/* Set coordinates of original segment */
seg->right = newseg->left;
seg->middle = (seg->left + seg->right) / 2.0;
/* Set colors of both segments */
g_clear_object (&newseg->right_color);
g_clear_object (&newseg->left_color);
newseg->right_color_type = seg->right_color_type;
newseg->right_color = seg->right_color;
seg->right_color_type = newseg->left_color_type = GIMP_GRADIENT_COLOR_FIXED;
seg->right_color = color;
newseg->left_color = gegl_color_duplicate (color);
/* Set parameters of new segment */
newseg->type = seg->type;
newseg->color = seg->color;
/* Done */
if (newl) *newl = seg;
if (newr) *newr = newseg;
gimp_data_thaw (GIMP_DATA (gradient));
}
GimpGradient *
gimp_gradient_flatten (GimpGradient *gradient,
GimpContext *context)
{
GimpGradient *flat;
GimpGradientSegment *seg;
g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), NULL);
g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL);
flat = GIMP_GRADIENT (gimp_data_duplicate (GIMP_DATA (gradient)));
for (seg = flat->segments; seg; seg = seg->next)
{
GeglColor *color;
color = gimp_gradient_segment_get_left_flat_color (gradient, context, seg);
g_clear_object (&seg->left_color);
seg->left_color = color;
seg->left_color_type = GIMP_GRADIENT_COLOR_FIXED;
color = gimp_gradient_segment_get_right_flat_color (gradient, context, seg);
g_clear_object (&seg->right_color);
seg->right_color = color;
seg->right_color_type = GIMP_GRADIENT_COLOR_FIXED;
}
return flat;
}
/* gradient segment functions */
GimpGradientSegment *
gimp_gradient_segment_new (void)
{
GimpGradientSegment *seg;
seg = g_slice_new0 (GimpGradientSegment);
seg->left = 0.0;
seg->middle = 0.5;
seg->right = 1.0;
seg->left_color_type = GIMP_GRADIENT_COLOR_FIXED;
seg->left_color = gegl_color_new ("black");
seg->right_color_type = GIMP_GRADIENT_COLOR_FIXED;
seg->right_color = gegl_color_new ("white");
seg->type = GIMP_GRADIENT_SEGMENT_LINEAR;
seg->color = GIMP_GRADIENT_SEGMENT_RGB;
seg->prev = seg->next = NULL;
return seg;
}
void
gimp_gradient_segment_free (GimpGradientSegment *seg)
{
g_return_if_fail (seg != NULL);
g_clear_object (&seg->right_color);
g_clear_object (&seg->left_color);
g_slice_free (GimpGradientSegment, seg);
}
void
gimp_gradient_segments_free (GimpGradientSegment *seg)
{
GimpGradientSegment *current = seg;
GimpGradientSegment *next;
g_return_if_fail (seg != NULL);
if (seg->prev)
seg->prev->next = NULL;
do
{
next = current->next;
gimp_gradient_segment_free (current);
current = next;
}
while (next);
}
GimpGradientSegment *
gimp_gradient_segment_get_last (GimpGradientSegment *seg)
{
if (! seg)
return NULL;
while (seg->next)
seg = seg->next;
return seg;
}
GimpGradientSegment *
gimp_gradient_segment_get_first (GimpGradientSegment *seg)
{
if (! seg)
return NULL;
while (seg->prev)
seg = seg->prev;
return seg;
}
GimpGradientSegment *
gimp_gradient_segment_get_nth (GimpGradientSegment *seg,
gint index)
{
gint i = 0;
g_return_val_if_fail (index >= 0, NULL);
if (! seg)
return NULL;
while (seg && (i < index))
{
seg = seg->next;
i++;
}
if (i == index)
return seg;
return NULL;
}
void
gimp_gradient_segment_split_midpoint (GimpGradient *gradient,
GimpContext *context,
GimpGradientSegment *lseg,
GimpGradientBlendColorSpace blend_color_space,
GimpGradientSegment **newl,
GimpGradientSegment **newr)
{
g_return_if_fail (GIMP_IS_GRADIENT (gradient));
g_return_if_fail (GIMP_IS_CONTEXT (context));
g_return_if_fail (lseg != NULL);
g_return_if_fail (newl != NULL);
g_return_if_fail (newr != NULL);
gimp_gradient_split_at (gradient, context, lseg, lseg->middle,
blend_color_space, newl, newr);
}
void
gimp_gradient_segment_split_uniform (GimpGradient *gradient,
GimpContext *context,
GimpGradientSegment *lseg,
gint parts,
GimpGradientBlendColorSpace blend_color_space,
GimpGradientSegment **newl,
GimpGradientSegment **newr)
{
GimpGradientSegment *seg, *prev, *tmp;
gdouble seg_len;
gint i;
g_return_if_fail (GIMP_IS_GRADIENT (gradient));
g_return_if_fail (GIMP_IS_CONTEXT (context));
g_return_if_fail (lseg != NULL);
g_return_if_fail (newl != NULL);
g_return_if_fail (newr != NULL);
gimp_data_freeze (GIMP_DATA (gradient));
seg_len = (lseg->right - lseg->left) / parts; /* Length of divisions */
seg = NULL;
prev = NULL;
tmp = NULL;
for (i = 0; i < parts; i++)
{
GeglColor *color = NULL;
seg = gimp_gradient_segment_new ();
if (i == 0)
tmp = seg; /* Remember first segment */
seg->left = lseg->left + i * seg_len;
seg->right = lseg->left + (i + 1) * seg_len;
seg->middle = (seg->left + seg->right) / 2.0;
seg->left_color_type = GIMP_GRADIENT_COLOR_FIXED;
seg->right_color_type = GIMP_GRADIENT_COLOR_FIXED;
gimp_gradient_get_color_at (gradient, context, lseg,
seg->left, FALSE, blend_color_space,
&color);
g_clear_object (&seg->left_color);
seg->left_color = color;
color = NULL;
gimp_gradient_get_color_at (gradient, context, lseg,
seg->right, FALSE, blend_color_space,
&color);
g_clear_object (&seg->right_color);
seg->right_color = color;
seg->type = lseg->type;
seg->color = lseg->color;
seg->prev = prev;
seg->next = NULL;
if (prev)
prev->next = seg;
prev = seg;
}
/* Fix edges */
tmp->left_color_type = lseg->left_color_type;
g_clear_object (&tmp->left_color);
tmp->left_color = gegl_color_duplicate (lseg->left_color);
seg->right_color_type = lseg->right_color_type;
g_clear_object (&seg->right_color);
seg->right_color = gegl_color_duplicate (lseg->right_color);
tmp->left = lseg->left;
seg->right = lseg->right; /* To squish accumulative error */
/* Link in list */
tmp->prev = lseg->prev;
seg->next = lseg->next;
if (lseg->prev)
lseg->prev->next = tmp;
else
gradient->segments = tmp; /* We are on leftmost segment */
if (lseg->next)
lseg->next->prev = seg;
/* Done */
*newl = tmp;
*newr = seg;
/* Delete old segment */
gimp_gradient_segment_free (lseg);
gimp_data_thaw (GIMP_DATA (gradient));
}
GeglColor *
gimp_gradient_segment_get_left_color (GimpGradient *gradient,
GimpGradientSegment *seg)
{
g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), NULL);
g_return_val_if_fail (seg != NULL, NULL);
return seg->left_color;
}
void
gimp_gradient_segment_set_left_color (GimpGradient *gradient,
GimpGradientSegment *seg,
GeglColor *color)
{
g_return_if_fail (GIMP_IS_GRADIENT (gradient));
g_return_if_fail (seg != NULL);
g_return_if_fail (GEGL_IS_COLOR (color));
gimp_data_freeze (GIMP_DATA (gradient));
gimp_gradient_segment_range_blend (gradient, seg, seg,
color, seg->right_color,
TRUE, TRUE);
gimp_data_thaw (GIMP_DATA (gradient));
}
GeglColor *
gimp_gradient_segment_get_right_color (GimpGradient *gradient,
GimpGradientSegment *seg)
{
g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), NULL);
g_return_val_if_fail (seg != NULL, NULL);
return seg->right_color;
}
void
gimp_gradient_segment_set_right_color (GimpGradient *gradient,
GimpGradientSegment *seg,
GeglColor *color)
{
g_return_if_fail (GIMP_IS_GRADIENT (gradient));
g_return_if_fail (seg != NULL);
g_return_if_fail (GEGL_IS_COLOR (color));
gimp_data_freeze (GIMP_DATA (gradient));
gimp_gradient_segment_range_blend (gradient, seg, seg,
seg->left_color, color,
TRUE, TRUE);
gimp_data_thaw (GIMP_DATA (gradient));
}
GimpGradientColor
gimp_gradient_segment_get_left_color_type (GimpGradient *gradient,
GimpGradientSegment *seg)
{
g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), 0);
g_return_val_if_fail (seg != NULL, 0);
return seg->left_color_type;
}
void
gimp_gradient_segment_set_left_color_type (GimpGradient *gradient,
GimpGradientSegment *seg,
GimpGradientColor color_type)
{
g_return_if_fail (GIMP_IS_GRADIENT (gradient));
g_return_if_fail (seg != NULL);
gimp_data_freeze (GIMP_DATA (gradient));
seg->left_color_type = color_type;
gimp_data_thaw (GIMP_DATA (gradient));
}
GimpGradientColor
gimp_gradient_segment_get_right_color_type (GimpGradient *gradient,
GimpGradientSegment *seg)
{
g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), 0);
g_return_val_if_fail (seg != NULL, 0);
return seg->right_color_type;
}
void
gimp_gradient_segment_set_right_color_type (GimpGradient *gradient,
GimpGradientSegment *seg,
GimpGradientColor color_type)
{
g_return_if_fail (GIMP_IS_GRADIENT (gradient));
g_return_if_fail (seg != NULL);
gimp_data_freeze (GIMP_DATA (gradient));
seg->right_color_type = color_type;
gimp_data_thaw (GIMP_DATA (gradient));
}
GeglColor *
gimp_gradient_segment_get_left_flat_color (GimpGradient *gradient,
GimpContext *context,
GimpGradientSegment *seg)
{
g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), NULL);
g_return_val_if_fail (seg != NULL, NULL);
return gimp_gradient_get_flat_color (context,
seg->left_color, seg->left_color_type);
}
GeglColor *
gimp_gradient_segment_get_right_flat_color (GimpGradient *gradient,
GimpContext *context,
GimpGradientSegment *seg)
{
g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), NULL);
g_return_val_if_fail (seg != NULL, NULL);
return gimp_gradient_get_flat_color (context,
seg->right_color, seg->right_color_type);
}
gdouble
gimp_gradient_segment_get_left_pos (GimpGradient *gradient,
GimpGradientSegment *seg)
{
g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), 0.0);
g_return_val_if_fail (seg != NULL, 0.0);
return seg->left;
}
gdouble
gimp_gradient_segment_set_left_pos (GimpGradient *gradient,
GimpGradientSegment *seg,
gdouble pos)
{
gdouble final_pos;
g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), 0.0);
g_return_val_if_fail (seg != NULL, 0.0);
if (seg->prev == NULL)
{
final_pos = 0;
}
else
{
gimp_data_freeze (GIMP_DATA (gradient));
final_pos = seg->prev->right = seg->left =
CLAMP (pos,
seg->prev->middle + EPSILON,
seg->middle - EPSILON);
gimp_data_thaw (GIMP_DATA (gradient));
}
return final_pos;
}
gdouble
gimp_gradient_segment_get_right_pos (GimpGradient *gradient,
GimpGradientSegment *seg)
{
g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), 0.0);
g_return_val_if_fail (seg != NULL, 0.0);
return seg->right;
}
gdouble
gimp_gradient_segment_set_right_pos (GimpGradient *gradient,
GimpGradientSegment *seg,
gdouble pos)
{
gdouble final_pos;
g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), 0.0);
g_return_val_if_fail (seg != NULL, 0.0);
if (seg->next == NULL)
{
final_pos = 1.0;
}
else
{
gimp_data_freeze (GIMP_DATA (gradient));
final_pos = seg->next->left = seg->right =
CLAMP (pos,
seg->middle + EPSILON,
seg->next->middle - EPSILON);
gimp_data_thaw (GIMP_DATA (gradient));
}
return final_pos;
}
gdouble
gimp_gradient_segment_get_middle_pos (GimpGradient *gradient,
GimpGradientSegment *seg)
{
g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), 0.0);
g_return_val_if_fail (seg != NULL, 0.0);
return seg->middle;
}
gdouble
gimp_gradient_segment_set_middle_pos (GimpGradient *gradient,
GimpGradientSegment *seg,
gdouble pos)
{
gdouble final_pos;
g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), 0.0);
g_return_val_if_fail (seg != NULL, 0.0);
gimp_data_freeze (GIMP_DATA (gradient));
final_pos = seg->middle =
CLAMP (pos,
seg->left + EPSILON,
seg->right - EPSILON);
gimp_data_thaw (GIMP_DATA (gradient));
return final_pos;
}
GimpGradientSegmentType
gimp_gradient_segment_get_blending_function (GimpGradient *gradient,
GimpGradientSegment *seg)
{
g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), 0);
return seg->type;
}
GimpGradientSegmentColor
gimp_gradient_segment_get_coloring_type (GimpGradient *gradient,
GimpGradientSegment *seg)
{
g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), 0);
return seg->color;
}
gint
gimp_gradient_segment_range_get_n_segments (GimpGradient *gradient,
GimpGradientSegment *range_l,
GimpGradientSegment *range_r)
{
gint n_segments = 0;
g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), 0);
g_return_val_if_fail (range_l != NULL, 0);
for (; range_l != range_r; range_l = range_l->next)
n_segments++;
if (range_r != NULL)
n_segments++;
return n_segments;
}
void
gimp_gradient_segment_range_compress (GimpGradient *gradient,
GimpGradientSegment *range_l,
GimpGradientSegment *range_r,
gdouble new_l,
gdouble new_r)
{
gdouble orig_l, orig_r;
GimpGradientSegment *seg, *aseg;
g_return_if_fail (GIMP_IS_GRADIENT (gradient));
g_return_if_fail (range_l != NULL);
gimp_data_freeze (GIMP_DATA (gradient));
if (! range_r)
range_r = gimp_gradient_segment_get_last (range_l);
orig_l = range_l->left;
orig_r = range_r->right;
if (orig_r - orig_l > EPSILON)
{
gdouble scale;
scale = (new_r - new_l) / (orig_r - orig_l);
seg = range_l;
do
{
if (seg->prev)
seg->left = new_l + (seg->left - orig_l) * scale;
seg->middle = new_l + (seg->middle - orig_l) * scale;
if (seg->next)
seg->right = new_l + (seg->right - orig_l) * scale;
/* Next */
aseg = seg;
seg = seg->next;
}
while (aseg != range_r);
}
else
{
gint n;
gint i;
n = gimp_gradient_segment_range_get_n_segments (gradient,
range_l, range_r);
for (i = 0, seg = range_l; i < n; i++, seg = seg->next)
{
if (seg->prev)
seg->left = new_l + (new_r - new_l) * (i + 0.0) / n;
seg->middle = new_l + (new_r - new_l) * (i + 0.5) / n;;
if (seg->next)
seg->right = new_l + (new_r - new_l) * (i + 1.0) / n;
}
}
/* Make sure that the left and right endpoints of the range are *exactly*
* equal to new_l and new_r; the above computations can introduce
* numerical inaccuracies.
*/
range_l->left = new_l;
range_l->right = new_r;
gimp_data_thaw (GIMP_DATA (gradient));
}
void
gimp_gradient_segment_range_blend (GimpGradient *gradient,
GimpGradientSegment *lseg,
GimpGradientSegment *rseg,
GeglColor *color1,
GeglColor *color2,
gboolean blend_colors,
gboolean blend_opacity)
{
gdouble left, len;
GimpGradientSegment *seg;
GimpGradientSegment *aseg;
gdouble red1;
gdouble green1;
gdouble blue1;
gdouble alpha1;
gdouble red2;
gdouble green2;
gdouble blue2;
gdouble alpha2;
gdouble dred;
gdouble dgreen;
gdouble dblue;
gdouble dalpha;
g_return_if_fail (GIMP_IS_GRADIENT (gradient));
g_return_if_fail (lseg != NULL);
gimp_data_freeze (GIMP_DATA (gradient));
if (! rseg)
rseg = gimp_gradient_segment_get_last (lseg);
/* TODO: currently doing this computation with sRGB value, because that's what
* was done historically. Is it right though?
*/
gegl_color_get_rgba_with_space (color1, &red1, &green1, &blue1, &alpha1, NULL);
gegl_color_get_rgba_with_space (color2, &red2, &green2, &blue2, &alpha2, NULL);
dred = red2 - red1;
dgreen = green2 - green1;
dblue = blue2 - blue1;
dalpha = alpha2 - alpha1;
left = lseg->left;
len = rseg->right - left;
seg = lseg;
do
{
gdouble seg_left_red;
gdouble seg_left_green;
gdouble seg_left_blue;
gdouble seg_left_alpha;
gdouble seg_right_red;
gdouble seg_right_green;
gdouble seg_right_blue;
gdouble seg_right_alpha;
if (blend_colors)
{
seg_left_red = red1 + (seg->left - left) / len * dred;
seg_left_green = green1 + (seg->left - left) / len * dgreen;
seg_left_blue = blue1 + (seg->left - left) / len * dblue;
seg_right_red = red1 + (seg->right - left) / len * dred;
seg_right_green = green1 + (seg->right - left) / len * dgreen;
seg_right_blue = blue1 + (seg->right - left) / len * dblue;
}
else if (blend_opacity)
{
gegl_color_get_rgba_with_space (seg->left_color, &seg_left_red, &seg_left_green,
&seg_left_blue, NULL, NULL);
gegl_color_get_rgba_with_space (seg->right_color, &seg_right_red, &seg_right_green,
&seg_right_blue, NULL, NULL);
}
if (blend_opacity)
{
seg_left_alpha = alpha1 + (seg->left - left) / len * dalpha;
seg_right_alpha = alpha1 + (seg->right - left) / len * dalpha;
}
else if (blend_colors)
{
gegl_color_get_rgba_with_space (seg->left_color, NULL, NULL, NULL, &seg_left_alpha, NULL);
gegl_color_get_rgba_with_space (seg->right_color, NULL, NULL, NULL, &seg_right_alpha, NULL);
}
if (blend_colors || blend_opacity)
{
gegl_color_set_rgba_with_space (seg->left_color, seg_left_red, seg_left_green,
seg_left_blue, seg_left_alpha, NULL);
gegl_color_set_rgba_with_space (seg->right_color, seg_right_red, seg_right_green,
seg_right_blue, seg_right_alpha, NULL);
}
aseg = seg;
seg = seg->next;
}
while (aseg != rseg);
gimp_data_thaw (GIMP_DATA (gradient));
}
void
gimp_gradient_segment_range_set_blending_function (GimpGradient *gradient,
GimpGradientSegment *start_seg,
GimpGradientSegment *end_seg,
GimpGradientSegmentType new_type)
{
GimpGradientSegment *seg;
gboolean reached_last_segment = FALSE;
g_return_if_fail (GIMP_IS_GRADIENT (gradient));
gimp_data_freeze (GIMP_DATA (gradient));
seg = start_seg;
while (seg && ! reached_last_segment)
{
if (seg == end_seg)
reached_last_segment = TRUE;
seg->type = new_type;
seg = seg->next;
}
gimp_data_thaw (GIMP_DATA (gradient));
}
void
gimp_gradient_segment_range_set_coloring_type (GimpGradient *gradient,
GimpGradientSegment *start_seg,
GimpGradientSegment *end_seg,
GimpGradientSegmentColor new_color)
{
GimpGradientSegment *seg;
gboolean reached_last_segment = FALSE;
g_return_if_fail (GIMP_IS_GRADIENT (gradient));
gimp_data_freeze (GIMP_DATA (gradient));
seg = start_seg;
while (seg && ! reached_last_segment)
{
if (seg == end_seg)
reached_last_segment = TRUE;
seg->color = new_color;
seg = seg->next;
}
gimp_data_thaw (GIMP_DATA (gradient));
}
void
gimp_gradient_segment_range_flip (GimpGradient *gradient,
GimpGradientSegment *start_seg,
GimpGradientSegment *end_seg,
GimpGradientSegment **final_start_seg,
GimpGradientSegment **final_end_seg)
{
GimpGradientSegment *oseg, *oaseg;
GimpGradientSegment *seg, *prev, *tmp;
GimpGradientSegment *lseg, *rseg;
gdouble left, right;
g_return_if_fail (GIMP_IS_GRADIENT (gradient));
gimp_data_freeze (GIMP_DATA (gradient));
if (! end_seg)
end_seg = gimp_gradient_segment_get_last (start_seg);
left = start_seg->left;
right = end_seg->right;
/* Build flipped segments */
prev = NULL;
oseg = end_seg;
tmp = NULL;
do
{
seg = gimp_gradient_segment_new ();
if (prev == NULL)
{
seg->left = left;
tmp = seg; /* Remember first segment */
}
else
{
seg->left = left + right - oseg->right;
}
seg->middle = left + right - oseg->middle;
seg->right = left + right - oseg->left;
g_clear_object (&seg->left_color);
g_clear_object (&seg->right_color);
seg->left_color_type = oseg->right_color_type;
seg->left_color = gegl_color_duplicate (oseg->right_color);
seg->right_color_type = oseg->left_color_type;
seg->right_color = gegl_color_duplicate (oseg->left_color);
switch (oseg->type)
{
case GIMP_GRADIENT_SEGMENT_SPHERE_INCREASING:
seg->type = GIMP_GRADIENT_SEGMENT_SPHERE_DECREASING;
break;
case GIMP_GRADIENT_SEGMENT_SPHERE_DECREASING:
seg->type = GIMP_GRADIENT_SEGMENT_SPHERE_INCREASING;
break;
default:
seg->type = oseg->type;
}
switch (oseg->color)
{
case GIMP_GRADIENT_SEGMENT_HSV_CCW:
seg->color = GIMP_GRADIENT_SEGMENT_HSV_CW;
break;
case GIMP_GRADIENT_SEGMENT_HSV_CW:
seg->color = GIMP_GRADIENT_SEGMENT_HSV_CCW;
break;
default:
seg->color = oseg->color;
}
seg->prev = prev;
seg->next = NULL;
if (prev)
prev->next = seg;
prev = seg;
oaseg = oseg;
oseg = oseg->prev; /* Move backwards! */
}
while (oaseg != start_seg);
seg->right = right; /* Squish accumulative error */
/* Free old segments */
lseg = start_seg->prev;
rseg = end_seg->next;
oseg = start_seg;
do
{
oaseg = oseg->next;
gimp_gradient_segment_free (oseg);
oseg = oaseg;
}
while (oaseg != rseg);
/* Link in new segments */
if (lseg)
lseg->next = tmp;
else
gradient->segments = tmp;
tmp->prev = lseg;
seg->next = rseg;
if (rseg)
rseg->prev = seg;
/* Reset selection */
if (final_start_seg)
*final_start_seg = tmp;
if (final_end_seg)
*final_end_seg = seg;
/* Done */
gimp_data_thaw (GIMP_DATA (gradient));
}
void
gimp_gradient_segment_range_replicate (GimpGradient *gradient,
GimpGradientSegment *start_seg,
GimpGradientSegment *end_seg,
gint replicate_times,
GimpGradientSegment **final_start_seg,
GimpGradientSegment **final_end_seg)
{
gdouble sel_left, sel_right, sel_len;
gdouble new_left;
gdouble factor;
GimpGradientSegment *prev, *seg, *tmp;
GimpGradientSegment *oseg, *oaseg;
GimpGradientSegment *lseg, *rseg;
gint i;
g_return_if_fail (GIMP_IS_GRADIENT (gradient));
if (! end_seg)
end_seg = gimp_gradient_segment_get_last (start_seg);
if (replicate_times < 2)
{
*final_start_seg = start_seg;
*final_end_seg = end_seg;
return;
}
gimp_data_freeze (GIMP_DATA (gradient));
/* Remember original parameters */
sel_left = start_seg->left;
sel_right = end_seg->right;
sel_len = sel_right - sel_left;
factor = 1.0 / replicate_times;
/* Build replicated segments */
prev = NULL;
seg = NULL;
tmp = NULL;
for (i = 0; i < replicate_times; i++)
{
/* Build one cycle */
new_left = sel_left + i * factor * sel_len;
oseg = start_seg;
do
{
seg = gimp_gradient_segment_new ();
if (prev == NULL)
{
seg->left = sel_left;
tmp = seg; /* Remember first segment */
}
else
{
seg->left = new_left + factor * (oseg->left - sel_left);
}
seg->middle = new_left + factor * (oseg->middle - sel_left);
seg->right = new_left + factor * (oseg->right - sel_left);
g_clear_object (&seg->left_color);
g_clear_object (&seg->right_color);
seg->left_color_type = oseg->left_color_type;
seg->left_color = gegl_color_duplicate (oseg->left_color);
seg->right_color_type = oseg->right_color_type;
seg->right_color = gegl_color_duplicate (oseg->right_color);
seg->type = oseg->type;
seg->color = oseg->color;
seg->prev = prev;
seg->next = NULL;
if (prev)
prev->next = seg;
prev = seg;
oaseg = oseg;
oseg = oseg->next;
}
while (oaseg != end_seg);
}
seg->right = sel_right; /* Squish accumulative error */
/* Free old segments */
lseg = start_seg->prev;
rseg = end_seg->next;
oseg = start_seg;
do
{
oaseg = oseg->next;
gimp_gradient_segment_free (oseg);
oseg = oaseg;
}
while (oaseg != rseg);
/* Link in new segments */
if (lseg)
lseg->next = tmp;
else
gradient->segments = tmp;
tmp->prev = lseg;
seg->next = rseg;
if (rseg)
rseg->prev = seg;
/* Reset selection */
if (final_start_seg)
*final_start_seg = tmp;
if (final_end_seg)
*final_end_seg = seg;
/* Done */
gimp_data_thaw (GIMP_DATA (gradient));
}
void
gimp_gradient_segment_range_split_midpoint (GimpGradient *gradient,
GimpContext *context,
GimpGradientSegment *start_seg,
GimpGradientSegment *end_seg,
GimpGradientBlendColorSpace blend_color_space,
GimpGradientSegment **final_start_seg,
GimpGradientSegment **final_end_seg)
{
GimpGradientSegment *seg, *lseg, *rseg;
g_return_if_fail (GIMP_IS_GRADIENT (gradient));
g_return_if_fail (GIMP_IS_CONTEXT (context));
gimp_data_freeze (GIMP_DATA (gradient));
if (! end_seg)
end_seg = gimp_gradient_segment_get_last (start_seg);
seg = start_seg;
do
{
gimp_gradient_segment_split_midpoint (gradient, context,
seg, blend_color_space,
&lseg, &rseg);
seg = rseg->next;
}
while (lseg != end_seg);
if (final_start_seg)
*final_start_seg = start_seg;
if (final_end_seg)
*final_end_seg = rseg;
gimp_data_thaw (GIMP_DATA (gradient));
}
void
gimp_gradient_segment_range_split_uniform (GimpGradient *gradient,
GimpContext *context,
GimpGradientSegment *start_seg,
GimpGradientSegment *end_seg,
gint parts,
GimpGradientBlendColorSpace blend_color_space,
GimpGradientSegment **final_start_seg,
GimpGradientSegment **final_end_seg)
{
GimpGradientSegment *seg, *aseg, *lseg, *rseg, *lsel;
g_return_if_fail (GIMP_IS_GRADIENT (gradient));
g_return_if_fail (GIMP_IS_CONTEXT (context));
if (! end_seg)
end_seg = gimp_gradient_segment_get_last (start_seg);
if (parts < 2)
{
*final_start_seg = start_seg;
*final_end_seg = end_seg;
return;
}
gimp_data_freeze (GIMP_DATA (gradient));
seg = start_seg;
lsel = NULL;
do
{
aseg = seg;
gimp_gradient_segment_split_uniform (gradient, context, seg,
parts, blend_color_space,
&lseg, &rseg);
if (seg == start_seg)
lsel = lseg;
seg = rseg->next;
}
while (aseg != end_seg);
if (final_start_seg)
*final_start_seg = lsel;
if (final_end_seg)
*final_end_seg = rseg;
gimp_data_thaw (GIMP_DATA (gradient));
}
void
gimp_gradient_segment_range_delete (GimpGradient *gradient,
GimpGradientSegment *start_seg,
GimpGradientSegment *end_seg,
GimpGradientSegment **final_start_seg,
GimpGradientSegment **final_end_seg)
{
GimpGradientSegment *lseg, *rseg, *seg, *aseg, *next;
gdouble join;
g_return_if_fail (GIMP_IS_GRADIENT (gradient));
if (! end_seg)
end_seg = gimp_gradient_segment_get_last (start_seg);
/* Remember segments to the left and to the right of the selection */
lseg = start_seg->prev;
rseg = end_seg->next;
/* Cannot delete all the segments in the gradient */
if ((lseg == NULL) && (rseg == NULL))
goto premature_return;
gimp_data_freeze (GIMP_DATA (gradient));
/* Calculate join point */
join = (start_seg->left +
end_seg->right) / 2.0;
if (lseg == NULL)
join = 0.0;
else if (rseg == NULL)
join = 1.0;
/* Move segments */
if (lseg != NULL)
gimp_gradient_segment_range_compress (gradient, lseg, lseg,
lseg->left, join);
if (rseg != NULL)
gimp_gradient_segment_range_compress (gradient, rseg, rseg,
join, rseg->right);
/* Link */
if (lseg)
lseg->next = rseg;
if (rseg)
rseg->prev = lseg;
/* Delete old segments */
seg = start_seg;
do
{
next = seg->next;
aseg = seg;
gimp_gradient_segment_free (seg);
seg = next;
}
while (aseg != end_seg);
/* Change selection */
if (rseg)
{
if (final_start_seg)
*final_start_seg = rseg;
if (final_end_seg)
*final_end_seg = rseg;
}
else
{
if (final_start_seg)
*final_start_seg = lseg;
if (final_end_seg)
*final_end_seg = lseg;
}
if (lseg == NULL)
gradient->segments = rseg;
gimp_data_thaw (GIMP_DATA (gradient));
return;
premature_return:
if (final_start_seg)
*final_start_seg = start_seg;
if (final_end_seg)
*final_end_seg = end_seg;
}
void
gimp_gradient_segment_range_merge (GimpGradient *gradient,
GimpGradientSegment *start_seg,
GimpGradientSegment *end_seg,
GimpGradientSegment **final_start_seg,
GimpGradientSegment **final_end_seg)
{
GimpGradientSegment *seg;
g_return_if_fail (GIMP_IS_GRADIENT (gradient));
if (! end_seg)
end_seg = gimp_gradient_segment_get_last (start_seg);
gimp_data_freeze (GIMP_DATA (gradient));
if (start_seg == end_seg)
goto out;
/* Copy the end segment's right position and color to the start segment */
start_seg->right = end_seg->right;
start_seg->right_color_type = end_seg->right_color_type;
g_clear_object (&start_seg->right_color);
start_seg->right_color = gegl_color_duplicate (end_seg->right_color);
/* Center the start segment's midpoint */
start_seg->middle = (start_seg->left + start_seg->right) / 2.0;
/* Remove range segments past the start segment from the segment list */
start_seg->next = end_seg->next;
if (start_seg->next)
start_seg->next->prev = start_seg;
/* Merge the range's blend function and coloring type, and free the rest of
* the segments.
*/
seg = end_seg;
while (seg != start_seg)
{
GimpGradientSegment *prev = seg->prev;
/* If the blend function and/or coloring type aren't uniform, reset them. */
if (seg->type != start_seg->type)
start_seg->type = GIMP_GRADIENT_SEGMENT_LINEAR;
if (seg->color != start_seg->color)
start_seg->color = GIMP_GRADIENT_SEGMENT_RGB;
gimp_gradient_segment_free (seg);
seg = prev;
}
out:
if (final_start_seg)
*final_start_seg = start_seg;
if (final_end_seg)
*final_end_seg = start_seg;
gimp_data_thaw (GIMP_DATA (gradient));
}
void
gimp_gradient_segment_range_recenter_handles (GimpGradient *gradient,
GimpGradientSegment *start_seg,
GimpGradientSegment *end_seg)
{
GimpGradientSegment *seg, *aseg;
g_return_if_fail (GIMP_IS_GRADIENT (gradient));
gimp_data_freeze (GIMP_DATA (gradient));
if (! end_seg)
end_seg = gimp_gradient_segment_get_last (start_seg);
seg = start_seg;
do
{
seg->middle = (seg->left + seg->right) / 2.0;
aseg = seg;
seg = seg->next;
}
while (aseg != end_seg);
gimp_data_thaw (GIMP_DATA (gradient));
}
void
gimp_gradient_segment_range_redistribute_handles (GimpGradient *gradient,
GimpGradientSegment *start_seg,
GimpGradientSegment *end_seg)
{
GimpGradientSegment *seg, *aseg;
gdouble left, right, seg_len;
gint num_segs;
gint i;
g_return_if_fail (GIMP_IS_GRADIENT (gradient));
gimp_data_freeze (GIMP_DATA (gradient));
if (! end_seg)
end_seg = gimp_gradient_segment_get_last (start_seg);
/* Count number of segments in selection */
num_segs = 0;
seg = start_seg;
do
{
num_segs++;
aseg = seg;
seg = seg->next;
}
while (aseg != end_seg);
/* Calculate new segment length */
left = start_seg->left;
right = end_seg->right;
seg_len = (right - left) / num_segs;
/* Redistribute */
seg = start_seg;
for (i = 0; i < num_segs; i++)
{
seg->left = left + i * seg_len;
seg->right = left + (i + 1) * seg_len;
seg->middle = (seg->left + seg->right) / 2.0;
seg = seg->next;
}
/* Fix endpoints to squish accumulative error */
start_seg->left = left;
end_seg->right = right;
gimp_data_thaw (GIMP_DATA (gradient));
}
gdouble
gimp_gradient_segment_range_move (GimpGradient *gradient,
GimpGradientSegment *range_l,
GimpGradientSegment *range_r,
gdouble delta,
gboolean control_compress)
{
gdouble lbound, rbound;
gint is_first, is_last;
GimpGradientSegment *seg, *aseg;
g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), 0.0);
gimp_data_freeze (GIMP_DATA (gradient));
if (! range_r)
range_r = gimp_gradient_segment_get_last (range_l);
/* First or last segments in gradient? */
is_first = (range_l->prev == NULL);
is_last = (range_r->next == NULL);
/* Calculate drag bounds */
if (! control_compress)
{
if (!is_first)
lbound = range_l->prev->middle + EPSILON;
else
lbound = range_l->left + EPSILON;
if (!is_last)
rbound = range_r->next->middle - EPSILON;
else
rbound = range_r->right - EPSILON;
}
else
{
if (!is_first)
lbound = range_l->prev->left + 2.0 * EPSILON;
else
lbound = range_l->left + EPSILON;
if (!is_last)
rbound = range_r->next->right - 2.0 * EPSILON;
else
rbound = range_r->right - EPSILON;
}
/* Fix the delta if necessary */
if (delta < 0.0)
{
if (!is_first)
{
if (range_l->left + delta < lbound)
delta = lbound - range_l->left;
}
else
if (range_l->middle + delta < lbound)
delta = lbound - range_l->middle;
}
else
{
if (!is_last)
{
if (range_r->right + delta > rbound)
delta = rbound - range_r->right;
}
else
if (range_r->middle + delta > rbound)
delta = rbound - range_r->middle;
}
/* Move all the segments inside the range */
seg = range_l;
do
{
if (!((seg == range_l) && is_first))
seg->left += delta;
seg->middle += delta;
if (!((seg == range_r) && is_last))
seg->right += delta;
/* Next */
aseg = seg;
seg = seg->next;
}
while (aseg != range_r);
/* Fix the segments that surround the range */
if (!is_first)
{
if (! control_compress)
range_l->prev->right = range_l->left;
else
gimp_gradient_segment_range_compress (gradient,
range_l->prev, range_l->prev,
range_l->prev->left, range_l->left);
}
if (!is_last)
{
if (! control_compress)
range_r->next->left = range_r->right;
else
gimp_gradient_segment_range_compress (gradient,
range_r->next, range_r->next,
range_r->right, range_r->next->right);
}
gimp_data_thaw (GIMP_DATA (gradient));
return delta;
}
/* private functions */
static inline GimpGradientSegment *
gimp_gradient_get_segment_at_internal (GimpGradient *gradient,
GimpGradientSegment *seg,
gdouble pos)
{
/* handle FP imprecision at the edges of the gradient */
pos = CLAMP (pos, 0.0, 1.0);
if (! seg)
seg = gradient->segments;
if (pos >= seg->left)
{
while (seg->next && pos >= seg->right)
seg = seg->next;
}
else
{
do
seg = seg->prev;
while (pos < seg->left);
}
return seg;
}
static GeglColor *
gimp_gradient_get_flat_color (GimpContext *context,
GeglColor *color,
GimpGradientColor color_type)
{
GeglColor *flat_color = NULL;
switch (color_type)
{
case GIMP_GRADIENT_COLOR_FIXED:
flat_color = gegl_color_duplicate (color);
break;
case GIMP_GRADIENT_COLOR_FOREGROUND:
case GIMP_GRADIENT_COLOR_FOREGROUND_TRANSPARENT:
flat_color = gegl_color_duplicate (gimp_context_get_foreground (context));
if (color_type == GIMP_GRADIENT_COLOR_FOREGROUND_TRANSPARENT)
gimp_color_set_alpha (flat_color, 0.0);
break;
case GIMP_GRADIENT_COLOR_BACKGROUND:
case GIMP_GRADIENT_COLOR_BACKGROUND_TRANSPARENT:
flat_color = gegl_color_duplicate (gimp_context_get_background (context));
if (color_type == GIMP_GRADIENT_COLOR_BACKGROUND_TRANSPARENT)
gimp_color_set_alpha (flat_color, 0.0);
break;
default:
g_return_val_if_reached (NULL);
}
return flat_color;
}
static inline gdouble
gimp_gradient_calc_linear_factor (gdouble middle,
gdouble pos)
{
if (pos <= middle)
{
if (middle < EPSILON)
return 0.0;
else
return 0.5 * pos / middle;
}
else
{
pos -= middle;
middle = 1.0 - middle;
if (middle < EPSILON)
return 1.0;
else
return 0.5 + 0.5 * pos / middle;
}
}
static inline gdouble
gimp_gradient_calc_curved_factor (gdouble middle,
gdouble pos)
{
if (middle < EPSILON)
return 1.0;
else if (1.0 - middle < EPSILON)
return 0.0;
return exp (-G_LN2 * log (pos) / log (middle));
}
static inline gdouble
gimp_gradient_calc_sine_factor (gdouble middle,
gdouble pos)
{
pos = gimp_gradient_calc_linear_factor (middle, pos);
return (sin ((-G_PI / 2.0) + G_PI * pos) + 1.0) / 2.0;
}
static inline gdouble
gimp_gradient_calc_sphere_increasing_factor (gdouble middle,
gdouble pos)
{
pos = gimp_gradient_calc_linear_factor (middle, pos) - 1.0;
/* Works for convex increasing and concave decreasing */
return sqrt (1.0 - pos * pos);
}
static inline gdouble
gimp_gradient_calc_sphere_decreasing_factor (gdouble middle,
gdouble pos)
{
pos = gimp_gradient_calc_linear_factor (middle, pos);
/* Works for convex decreasing and concave increasing */
return 1.0 - sqrt(1.0 - pos * pos);
}
static inline gdouble
gimp_gradient_calc_step_factor (gdouble middle,
gdouble pos)
{
return pos >= middle;
}