Gimp/app/core/gimpgradient.c
Jehan 7262faed16 app, pdb: allow rendering font previews with theme colors.
This also depends on the same settings to render brushes with theme
colors, because this is data, not user interface. Therefore following
theme colors is not necessarily what creators would expect.
2025-06-20 00:11:08 +02:00

2418 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,
GeglColor *color,
GeglColor *background);
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,
GeglColor *color G_GNUC_UNUSED,
GeglColor *background G_GNUC_UNUSED)
{
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 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;
}