summaryrefslogtreecommitdiffstats
path: root/app/core/gimpgradient.c
diff options
context:
space:
mode:
Diffstat (limited to 'app/core/gimpgradient.c')
-rw-r--r--app/core/gimpgradient.c2297
1 files changed, 2297 insertions, 0 deletions
diff --git a/app/core/gimpgradient.c b/app/core/gimpgradient.c
new file mode 100644
index 0000000..8265225
--- /dev/null
+++ b/app/core/gimpgradient.c
@@ -0,0 +1,2297 @@
+/* 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 void gimp_gradient_get_flat_color (GimpContext *context,
+ const GimpRGB *color,
+ GimpGradientColor color_type,
+ GimpRGB *flat_color);
+
+
+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 double"));
+ fish_cie_lab_to_srgb = babl_fish (babl_format ("CIE Lab double"),
+ 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;
+ GimpRGB color;
+
+ 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++)
+ {
+ seg = gimp_gradient_get_color_at (gradient, context, seg, cur_x,
+ FALSE,
+ GIMP_GRADIENT_BLEND_RGB_PERCEPTUAL,
+ &color);
+
+ *p++ = ROUND (color.r * 255.0);
+ *p++ = ROUND (color.g * 255.0);
+ *p++ = ROUND (color.b * 255.0);
+ *p++ = ROUND (color.a * 255.0);
+
+ cur_x += dx;
+ }
+
+ 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 ();
+
+ *cur = *orig; /* Copy everything */
+
+ cur->prev = prev;
+ cur->next = NULL;
+
+ 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;
+
+ while (segment)
+ {
+ 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));
+ g_checksum_update (checksum,
+ (const guchar *) &segment->left_color,
+ sizeof (segment->left_color));
+ g_checksum_update (checksum,
+ (const guchar *) &segment->right_color_type,
+ sizeof (segment->right_color_type));
+ g_checksum_update (checksum,
+ (const guchar *) &segment->right_color,
+ sizeof (segment->right_color));
+ 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)
+ {
+ standard_gradient = gimp_gradient_new (context, "Standard");
+
+ gimp_data_clean (standard_gradient);
+ gimp_data_make_internal (standard_gradient, "gimp-gradient-standard");
+
+ g_object_add_weak_pointer (G_OBJECT (standard_gradient),
+ (gpointer *) &standard_gradient);
+ }
+
+ 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: 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 the color
+ *
+ * If you are iterating over an gradient, you should pass the the
+ * return value from the last call for @seg.
+ *
+ * Return value: 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,
+ GimpRGB *color)
+{
+ gdouble factor = 0.0;
+ gdouble seg_len;
+ gdouble middle;
+ GimpRGB left_color;
+ GimpRGB right_color;
+ GimpRGB rgb;
+
+ /* type-check disabled to improve speed */
+ /* g_return_val_if_fail (GIMP_IS_GRADIENT (gradient), NULL); */
+ g_return_val_if_fail (color != NULL, 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)
+ {
+ gimp_gradient_segment_get_left_flat_color (gradient,
+ context, seg, &left_color);
+
+ gimp_gradient_segment_get_right_flat_color (gradient,
+ context, seg, &right_color);
+ }
+ else
+ {
+ left_color = seg->left_color;
+ right_color = seg->right_color;
+ }
+
+ /* Calculate color components */
+
+ if (seg->color == GIMP_GRADIENT_SEGMENT_RGB)
+ {
+ switch (blend_color_space)
+ {
+ case GIMP_GRADIENT_BLEND_CIE_LAB:
+ babl_process (fish_srgb_to_cie_lab,
+ &left_color, &left_color, 1);
+ babl_process (fish_srgb_to_cie_lab,
+ &right_color, &right_color, 1);
+ break;
+
+ case GIMP_GRADIENT_BLEND_RGB_LINEAR:
+ babl_process (fish_srgb_to_linear_rgb,
+ &left_color, &left_color, 1);
+ babl_process (fish_srgb_to_linear_rgb,
+ &right_color, &right_color, 1);
+
+ case GIMP_GRADIENT_BLEND_RGB_PERCEPTUAL:
+ break;
+ }
+
+ rgb.r = left_color.r + (right_color.r - left_color.r) * factor;
+ rgb.g = left_color.g + (right_color.g - left_color.g) * factor;
+ rgb.b = left_color.b + (right_color.b - left_color.b) * factor;
+
+ switch (blend_color_space)
+ {
+ case GIMP_GRADIENT_BLEND_CIE_LAB:
+ babl_process (fish_cie_lab_to_srgb,
+ &rgb, &rgb, 1);
+ break;
+
+ case GIMP_GRADIENT_BLEND_RGB_LINEAR:
+ babl_process (fish_linear_rgb_to_srgb,
+ &rgb, &rgb, 1);
+
+ case GIMP_GRADIENT_BLEND_RGB_PERCEPTUAL:
+ break;
+ }
+ }
+ else
+ {
+ GimpHSV left_hsv;
+ GimpHSV right_hsv;
+
+ gimp_rgb_to_hsv (&left_color, &left_hsv);
+ gimp_rgb_to_hsv (&right_color, &right_hsv);
+
+ left_hsv.s = left_hsv.s + (right_hsv.s - left_hsv.s) * factor;
+ left_hsv.v = left_hsv.v + (right_hsv.v - left_hsv.v) * factor;
+
+ switch (seg->color)
+ {
+ case GIMP_GRADIENT_SEGMENT_HSV_CCW:
+ if (left_hsv.h < right_hsv.h)
+ {
+ left_hsv.h += (right_hsv.h - left_hsv.h) * factor;
+ }
+ else
+ {
+ left_hsv.h += (1.0 - (left_hsv.h - right_hsv.h)) * factor;
+
+ if (left_hsv.h > 1.0)
+ left_hsv.h -= 1.0;
+ }
+ break;
+
+ case GIMP_GRADIENT_SEGMENT_HSV_CW:
+ if (right_hsv.h < left_hsv.h)
+ {
+ left_hsv.h -= (left_hsv.h - right_hsv.h) * factor;
+ }
+ else
+ {
+ left_hsv.h -= (1.0 - (right_hsv.h - left_hsv.h)) * factor;
+
+ if (left_hsv.h < 0.0)
+ left_hsv.h += 1.0;
+ }
+ break;
+
+ default:
+ g_warning ("%s: Unknown coloring mode %d",
+ G_STRFUNC, (gint) seg->color);
+ break;
+ }
+
+ gimp_hsv_to_rgb (&left_hsv, &rgb);
+ }
+
+ /* Calculate alpha */
+
+ rgb.a = left_color.a + (right_color.a - left_color.a) * factor;
+
+ *color = rgb;
+
+ 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)
+{
+ GimpRGB color;
+ 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 */
+
+ 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 = newseg->left_color = 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)
+ {
+ gimp_gradient_segment_get_left_flat_color (gradient,
+ context, seg,
+ &seg->left_color);
+
+ seg->left_color_type = GIMP_GRADIENT_COLOR_FIXED;
+
+ gimp_gradient_segment_get_right_flat_color (gradient,
+ context, seg,
+ &seg->right_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;
+ gimp_rgba_set (&seg->left_color, 0.0, 0.0, 0.0, 1.0);
+
+ seg->right_color_type = GIMP_GRADIENT_COLOR_FIXED;
+ gimp_rgba_set (&seg->right_color, 1.0, 1.0, 1.0, 1.0);
+
+ 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_slice_free (GimpGradientSegment, seg);
+}
+
+void
+gimp_gradient_segments_free (GimpGradientSegment *seg)
+{
+ g_return_if_fail (seg != NULL);
+
+ g_slice_free_chain (GimpGradientSegment, seg, 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++)
+ {
+ 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,
+ &seg->left_color);
+ gimp_gradient_get_color_at (gradient, context, lseg,
+ seg->right, FALSE, blend_color_space,
+ &seg->right_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;
+ tmp->left_color = lseg->left_color;
+
+ seg->right_color_type = lseg->right_color_type;
+ seg->right_color = 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));
+}
+
+void
+gimp_gradient_segment_get_left_color (GimpGradient *gradient,
+ GimpGradientSegment *seg,
+ GimpRGB *color)
+{
+ g_return_if_fail (GIMP_IS_GRADIENT (gradient));
+ g_return_if_fail (seg != NULL);
+ g_return_if_fail (color != NULL);
+
+ *color = seg->left_color;
+}
+
+void
+gimp_gradient_segment_set_left_color (GimpGradient *gradient,
+ GimpGradientSegment *seg,
+ const GimpRGB *color)
+{
+ g_return_if_fail (GIMP_IS_GRADIENT (gradient));
+ g_return_if_fail (seg != NULL);
+ g_return_if_fail (color != NULL);
+
+ 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));
+}
+
+void
+gimp_gradient_segment_get_right_color (GimpGradient *gradient,
+ GimpGradientSegment *seg,
+ GimpRGB *color)
+{
+ g_return_if_fail (GIMP_IS_GRADIENT (gradient));
+ g_return_if_fail (seg != NULL);
+ g_return_if_fail (color != NULL);
+
+ *color = seg->right_color;
+}
+
+void
+gimp_gradient_segment_set_right_color (GimpGradient *gradient,
+ GimpGradientSegment *seg,
+ const GimpRGB *color)
+{
+ g_return_if_fail (GIMP_IS_GRADIENT (gradient));
+ g_return_if_fail (seg != NULL);
+ g_return_if_fail (color != NULL);
+
+ 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));
+}
+
+void
+gimp_gradient_segment_get_left_flat_color (GimpGradient *gradient,
+ GimpContext *context,
+ GimpGradientSegment *seg,
+ GimpRGB *color)
+{
+ g_return_if_fail (GIMP_IS_GRADIENT (gradient));
+ g_return_if_fail (seg != NULL);
+ g_return_if_fail (color != NULL);
+
+ gimp_gradient_get_flat_color (context,
+ &seg->left_color, seg->left_color_type,
+ color);
+}
+
+void
+gimp_gradient_segment_get_right_flat_color (GimpGradient *gradient,
+ GimpContext *context,
+ GimpGradientSegment *seg,
+ GimpRGB *color)
+{
+ g_return_if_fail (GIMP_IS_GRADIENT (gradient));
+ g_return_if_fail (seg != NULL);
+ g_return_if_fail (color != NULL);
+
+ gimp_gradient_get_flat_color (context,
+ &seg->right_color, seg->right_color_type,
+ color);
+}
+
+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,
+ const GimpRGB *rgb1,
+ const GimpRGB *rgb2,
+ gboolean blend_colors,
+ gboolean blend_opacity)
+{
+ GimpRGB d;
+ gdouble left, len;
+ GimpGradientSegment *seg;
+ GimpGradientSegment *aseg;
+
+ 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);
+
+ d.r = rgb2->r - rgb1->r;
+ d.g = rgb2->g - rgb1->g;
+ d.b = rgb2->b - rgb1->b;
+ d.a = rgb2->a - rgb1->a;
+
+ left = lseg->left;
+ len = rseg->right - left;
+
+ seg = lseg;
+
+ do
+ {
+ if (blend_colors)
+ {
+ seg->left_color.r = rgb1->r + (seg->left - left) / len * d.r;
+ seg->left_color.g = rgb1->g + (seg->left - left) / len * d.g;
+ seg->left_color.b = rgb1->b + (seg->left - left) / len * d.b;
+
+ seg->right_color.r = rgb1->r + (seg->right - left) / len * d.r;
+ seg->right_color.g = rgb1->g + (seg->right - left) / len * d.g;
+ seg->right_color.b = rgb1->b + (seg->right - left) / len * d.b;
+ }
+
+ if (blend_opacity)
+ {
+ seg->left_color.a = rgb1->a + (seg->left - left) / len * d.a;
+ seg->right_color.a = rgb1->a + (seg->right - left) / len * d.a;
+ }
+
+ 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;
+
+ seg->left_color_type = oseg->right_color_type;
+ seg->left_color = oseg->right_color;
+
+ seg->right_color_type = oseg->left_color_type;
+ seg->right_color = 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);
+
+ seg->left_color_type = oseg->left_color_type;
+ seg->left_color = oseg->left_color;
+
+ seg->right_color_type = oseg->right_color_type;
+ seg->right_color = 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));
+
+ /* 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;
+ start_seg->right_color = 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;
+ }
+
+ 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 void
+gimp_gradient_get_flat_color (GimpContext *context,
+ const GimpRGB *color,
+ GimpGradientColor color_type,
+ GimpRGB *flat_color)
+{
+ switch (color_type)
+ {
+ case GIMP_GRADIENT_COLOR_FIXED:
+ *flat_color = *color;
+ break;
+
+ case GIMP_GRADIENT_COLOR_FOREGROUND:
+ case GIMP_GRADIENT_COLOR_FOREGROUND_TRANSPARENT:
+ gimp_context_get_foreground (context, flat_color);
+
+ if (color_type == GIMP_GRADIENT_COLOR_FOREGROUND_TRANSPARENT)
+ gimp_rgb_set_alpha (flat_color, 0.0);
+ break;
+
+ case GIMP_GRADIENT_COLOR_BACKGROUND:
+ case GIMP_GRADIENT_COLOR_BACKGROUND_TRANSPARENT:
+ gimp_context_get_background (context, flat_color);
+
+ if (color_type == GIMP_GRADIENT_COLOR_BACKGROUND_TRANSPARENT)
+ gimp_rgb_set_alpha (flat_color, 0.0);
+ break;
+ }
+}
+
+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;
+}