/* LIBGIMP - The GIMP Library
 * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
 *
 * gimpcolorscale.c
 * Copyright (C) 2002-2010  Sven Neumann <sven@gimp.org>
 *                          Michael Natterer <mitch@gimp.org>
 *
 * This library is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see
 * <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <string.h>

#include <gegl.h>
#include <gtk/gtk.h>

#include "libgimpbase/gimpbase.h"
#include "libgimpconfig/gimpconfig.h"
#include "libgimpcolor/gimpcolor.h"

#include "gimpwidgetstypes.h"

#include "gimpcairo-utils.h"
#include "gimpcolorscale.h"
#include "gimpwidgetsutils.h"


/**
 * SECTION: gimpcolorscale
 * @title: GimpColorScale
 * @short_description: Fancy colored sliders.
 *
 * Fancy colored sliders.
 **/


enum
{
  PROP_0,
  PROP_CHANNEL
};


typedef struct _GimpLCH  GimpLCH;

struct _GimpLCH
{
  gdouble l, c, h, a;
};


struct _GimpColorScalePrivate
{
  GimpColorConfig          *config;
  guchar                    oog_color[3];
  const Babl               *format;

  GimpColorSelectorChannel  channel;
  GeglColor                *color;

  guchar                   *buf;
  guint                     width;
  guint                     height;
  guint                     rowstride;

  gboolean                  needs_render;
};

#define GET_PRIVATE(obj) (((GimpColorScale *) (obj))->priv)


static void     gimp_color_scale_dispose             (GObject          *object);
static void     gimp_color_scale_finalize            (GObject          *object);
static void     gimp_color_scale_get_property        (GObject          *object,
                                                      guint             property_id,
                                                      GValue           *value,
                                                      GParamSpec       *pspec);
static void     gimp_color_scale_set_property        (GObject          *object,
                                                      guint             property_id,
                                                      const GValue     *value,
                                                      GParamSpec       *pspec);

static void     gimp_color_scale_size_allocate       (GtkWidget        *widget,
                                                      GtkAllocation    *allocation);
static gboolean gimp_color_scale_draw                (GtkWidget        *widget,
                                                      cairo_t          *cr);

static void     gimp_color_scale_render              (GimpColorScale   *scale);
static void     gimp_color_scale_render_alpha        (GimpColorScale   *scale);

static void     gimp_color_scale_notify_config       (GimpColorConfig  *config,
                                                      const GParamSpec *pspec,
                                                      GimpColorScale   *scale);


G_DEFINE_TYPE_WITH_PRIVATE (GimpColorScale, gimp_color_scale, GTK_TYPE_SCALE)

#define parent_class gimp_color_scale_parent_class

static const Babl *fish_lch_to_rgb   = NULL;
static const Babl *fish_hsv_to_rgb   = NULL;
static const Babl *fish_rgb_to_cairo = NULL;


static void
gimp_color_scale_class_init (GimpColorScaleClass *klass)
{
  GObjectClass   *object_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

  object_class->dispose       = gimp_color_scale_dispose;
  object_class->finalize      = gimp_color_scale_finalize;
  object_class->get_property  = gimp_color_scale_get_property;
  object_class->set_property  = gimp_color_scale_set_property;

  widget_class->size_allocate = gimp_color_scale_size_allocate;
  widget_class->draw          = gimp_color_scale_draw;

  /**
   * GimpColorScale:channel:
   *
   * The channel which is edited by the color scale.
   *
   * Since: 2.8
   */
  g_object_class_install_property (object_class, PROP_CHANNEL,
                                   g_param_spec_enum ("channel",
                                                      "Channel",
                                                      "The channel which is edited by the color scale",
                                                      GIMP_TYPE_COLOR_SELECTOR_CHANNEL,
                                                      GIMP_COLOR_SELECTOR_VALUE,
                                                      GIMP_PARAM_READWRITE |
                                                      G_PARAM_CONSTRUCT));

  gtk_widget_class_set_css_name (widget_class, "GimpColorScale");

  fish_lch_to_rgb   = babl_fish (babl_format ("CIE LCH(ab) double"),
                                 babl_format ("R'G'B' double"));
  fish_hsv_to_rgb   = babl_fish (babl_format ("HSV float"),
                                 babl_format ("R'G'B' double"));
  fish_rgb_to_cairo = babl_fish (babl_format ("R'G'B' u8"),
                                 babl_format ("cairo-RGB24"));
}

static void
gimp_color_scale_init (GimpColorScale *scale)
{
  GimpColorScalePrivate *priv;
  GtkRange              *range = GTK_RANGE (scale);
  GtkCssProvider        *css;

  scale->priv = gimp_color_scale_get_instance_private (scale);

  priv = scale->priv;

  gtk_widget_set_can_focus (GTK_WIDGET (scale), TRUE);

  gtk_range_set_slider_size_fixed (range, TRUE);
  gtk_range_set_flippable (GTK_RANGE (scale), TRUE);
  gtk_scale_set_draw_value (GTK_SCALE (scale), FALSE);

  priv->channel      = GIMP_COLOR_SELECTOR_VALUE;
  priv->needs_render = TRUE;

  gtk_orientable_set_orientation (GTK_ORIENTABLE (range),
                                  GTK_ORIENTATION_HORIZONTAL);

  priv->color = gegl_color_new ("black");

  css = gtk_css_provider_new ();
  gtk_css_provider_load_from_data (css,
                                   "GimpColorScale {"
                                   "  padding:    2px 12px 2px 12px;"
                                   "  min-width:  24px;"
                                   "  min-height: 24px;"
                                   "}\n"
                                   "GimpColorScale contents trough {"
                                   "  min-width:  20px;"
                                   "  min-height: 20px;"
                                   "}\n"
                                   "GimpColorScale contents trough slider {"
                                   "  min-width:  12px;"
                                   "  min-height: 12px;"
                                   "  margin:     -6px -6px -6px -6px;"
                                   "}",
                                   -1, NULL);
  gtk_style_context_add_provider (gtk_widget_get_style_context (GTK_WIDGET (scale)),
                                  GTK_STYLE_PROVIDER (css),
                                  GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
  g_object_unref (css);
}

static void
gimp_color_scale_dispose (GObject *object)
{
  GimpColorScale *scale = GIMP_COLOR_SCALE (object);

  gimp_color_scale_set_color_config (scale, NULL);

  G_OBJECT_CLASS (parent_class)->dispose (object);
}

static void
gimp_color_scale_finalize (GObject *object)
{
  GimpColorScalePrivate *priv = GET_PRIVATE (object);

  g_clear_pointer (&priv->buf, g_free);
  priv->width     = 0;
  priv->height    = 0;
  priv->rowstride = 0;
  g_object_unref (priv->color);

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
gimp_color_scale_get_property (GObject    *object,
                               guint       property_id,
                               GValue     *value,
                               GParamSpec *pspec)
{
  GimpColorScalePrivate *priv = GET_PRIVATE (object);

  switch (property_id)
    {
    case PROP_CHANNEL:
      g_value_set_enum (value, priv->channel);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gimp_color_scale_set_property (GObject      *object,
                               guint         property_id,
                               const GValue *value,
                               GParamSpec   *pspec)
{
  GimpColorScale *scale = GIMP_COLOR_SCALE (object);

  switch (property_id)
    {
    case PROP_CHANNEL:
      gimp_color_scale_set_channel (scale, g_value_get_enum (value));
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gimp_color_scale_size_allocate (GtkWidget     *widget,
                                GtkAllocation *allocation)
{
  GimpColorScalePrivate *priv  = GET_PRIVATE (widget);
  GtkRange              *range = GTK_RANGE (widget);
  GdkRectangle           range_rect;

  GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);

  gtk_range_get_range_rect (range, &range_rect);

  if (range_rect.width  != priv->width ||
      range_rect.height != priv->height)
    {
      priv->width  = range_rect.width;
      priv->height = range_rect.height;

      /* TODO: we should move to CAIRO_FORMAT_RGBA128F. */
      priv->rowstride = cairo_format_stride_for_width (CAIRO_FORMAT_RGB24, priv->width);

      g_free (priv->buf);
      priv->buf = g_new (guchar, 3 * priv->width * priv->height);

      priv->needs_render = TRUE;
    }
}

static gboolean
gimp_color_scale_draw (GtkWidget *widget,
                       cairo_t   *cr)
{
  GimpColorScale        *scale     = GIMP_COLOR_SCALE (widget);
  GimpColorScalePrivate *priv      = GET_PRIVATE (widget);
  GtkRange              *range     = GTK_RANGE (widget);
  GtkStyleContext       *context   = gtk_widget_get_style_context (widget);
  GdkRectangle           range_rect;
  GdkRectangle           area      = { 0, };
  cairo_surface_t       *buffer;
  guchar                *buf       = NULL;
  guchar                *src;
  guchar                *dest;
  gint                   slider_start;
  gint                   slider_end;
  gint                   slider_mid;
  gint                   slider_size;
  const Babl            *render_space;

  if (! priv->buf)
    return FALSE;

  gtk_range_get_range_rect (range, &range_rect);
  gtk_range_get_slider_range (range, &slider_start, &slider_end);

  slider_mid = slider_start + (slider_end - slider_start) / 2;
  slider_size = 6;

  if (priv->needs_render)
    {
      gimp_color_scale_render (scale);

      priv->needs_render = FALSE;
    }

  render_space = gimp_widget_get_render_space (widget, priv->config);
  fish_rgb_to_cairo = babl_fish (babl_format_with_space ("R'G'B' u8", priv->format),
                                 babl_format_with_space ("cairo-RGB24", render_space)),

  src  = priv->buf;
  buf  = g_new (guchar, priv->rowstride * priv->height);
  dest = buf;
  /* We convert per line because the cairo rowstride may be bigger than the
   * real contents.
   */
  for (gint i = 0; i < priv->height; i++)
    {
      babl_process (fish_rgb_to_cairo, src, dest, priv->width);
      src  += 3 * priv->width;
      dest += priv->rowstride;
    }

  buffer = cairo_image_surface_create_for_data (buf,
                                                CAIRO_FORMAT_RGB24,
                                                priv->width,
                                                priv->height,
                                                priv->rowstride);
  cairo_surface_set_user_data (buffer, NULL, buf, (cairo_destroy_func_t) g_free);

  switch (gtk_orientable_get_orientation (GTK_ORIENTABLE (range)))
    {
    case GTK_ORIENTATION_HORIZONTAL:
      cairo_set_source_surface (cr, buffer,
                                range_rect.x, range_rect.y);
      break;

    case GTK_ORIENTATION_VERTICAL:
      cairo_set_source_surface (cr, buffer,
                                range_rect.x, range_rect.y);
      break;
    }

  cairo_surface_destroy (buffer);

  if (! gtk_widget_is_sensitive (widget))
    {
      static cairo_pattern_t *pattern = NULL;

      if (! pattern)
        {
          static const guchar  stipple[] = { 0,   255, 0, 0,
                                             255, 0,   0, 0 };
          cairo_surface_t     *surface;
          gint                 stride;

          stride = cairo_format_stride_for_width (CAIRO_FORMAT_A8, 2);

          surface = cairo_image_surface_create_for_data ((guchar *) stipple,
                                                         CAIRO_FORMAT_A8,
                                                         2, 2, stride);
          pattern = cairo_pattern_create_for_surface (surface);
          cairo_surface_destroy (surface);

          cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
        }

      cairo_mask (cr, pattern);
    }
  else
    {
      cairo_paint (cr);
    }

  if (gtk_widget_has_focus (widget))
    gtk_render_focus (context, cr,
                      0, 0,
                      gtk_widget_get_allocated_width  (widget),
                      gtk_widget_get_allocated_height (widget));

  switch (gtk_orientable_get_orientation (GTK_ORIENTABLE (range)))
    {
    case GTK_ORIENTATION_HORIZONTAL:
      area.x      = slider_mid - slider_size;
      area.y      = range_rect.y;
      area.width  = 2 * slider_size;
      area.height = range_rect.height;
      break;

    case GTK_ORIENTATION_VERTICAL:
      area.x      = range_rect.x;
      area.y      = slider_mid - slider_size;
      area.width  = range_rect.width;
      area.height = 2 * slider_size;
      break;
    }

  if (gtk_widget_is_sensitive (widget))
    cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
  else
    cairo_set_source_rgb (cr, 0.2, 0.2, 0.2);

  switch (gtk_orientable_get_orientation (GTK_ORIENTABLE (range)))
    {
    case GTK_ORIENTATION_HORIZONTAL:
      cairo_move_to (cr, area.x, area.y);
      cairo_line_to (cr, area.x + area.width, area.y);
      cairo_line_to (cr,
                     area.x + area.width / 2 + 0.5,
                     area.y + slider_size);
      break;

    case GTK_ORIENTATION_VERTICAL:
      cairo_move_to (cr, area.x, area.y);
      cairo_line_to (cr, area.x, area.y + area.height);
      cairo_line_to (cr,
                     area.x + slider_size,
                     area.y + area.height / 2 + 0.5);
      break;
    }

  cairo_close_path (cr);
  cairo_fill (cr);

  if (gtk_widget_is_sensitive (widget))
    cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
  else
    cairo_set_source_rgb (cr, 0.8, 0.8, 0.8);

  switch (gtk_orientable_get_orientation (GTK_ORIENTABLE (range)))
    {
    case GTK_ORIENTATION_HORIZONTAL:
      cairo_move_to (cr, area.x, area.y + area.height);
      cairo_line_to (cr, area.x + area.width, area.y + area.height);
      cairo_line_to (cr,
                     area.x + area.width / 2 + 0.5,
                     area.y + area.height - slider_size);
      break;

    case GTK_ORIENTATION_VERTICAL:
      cairo_move_to (cr, area.x + area.width, area.y);
      cairo_line_to (cr, area.x + area.width, area.y + area.height);
      cairo_line_to (cr,
                     area.x + area.width - slider_size,
                     area.y + area.height / 2 + 0.5);
      break;
    }

  cairo_close_path (cr);
  cairo_fill (cr);

  return FALSE;
}

/**
 * gimp_color_scale_new:
 * @orientation: the scale's orientation (horizontal or vertical)
 * @channel: the scale's color channel
 *
 * Creates a new #GimpColorScale widget.
 *
 * Returns: a new #GimpColorScale widget
 **/
GtkWidget *
gimp_color_scale_new (GtkOrientation            orientation,
                      GimpColorSelectorChannel  channel)
{
  GimpColorScale *scale = g_object_new (GIMP_TYPE_COLOR_SCALE,
                                        "orientation", orientation,
                                        "channel",     channel,
                                        NULL);

  gtk_range_set_flippable (GTK_RANGE (scale),
                           orientation == GTK_ORIENTATION_HORIZONTAL);

  return GTK_WIDGET (scale);
}

/**
 * gimp_color_scale_set_format:
 * @scale:  a #GimpColorScale widget
 * @format: the Babl format represented by @scale.
 *
 * Changes the color format displayed by the @scale.
 **/
void
gimp_color_scale_set_format (GimpColorScale *scale,
                             const Babl     *format)
{
  if (scale->priv->format != format)
    {
      scale->priv->format = format;
      fish_lch_to_rgb   = babl_fish (babl_format ("CIE LCH(ab) double"),
                                     babl_format_with_space ("R'G'B' double", format));
      fish_hsv_to_rgb   = babl_fish (babl_format_with_space ("HSV float", format),
                                     babl_format_with_space ("R'G'B' double", format));
      scale->priv->needs_render = TRUE;
      gtk_widget_queue_draw (GTK_WIDGET (scale));
    }
}

/**
 * gimp_color_scale_set_channel:
 * @scale: a #GimpColorScale widget
 * @channel: the new color channel
 *
 * Changes the color channel displayed by the @scale.
 **/
void
gimp_color_scale_set_channel (GimpColorScale           *scale,
                              GimpColorSelectorChannel  channel)
{
  GimpColorScalePrivate *priv;

  g_return_if_fail (GIMP_IS_COLOR_SCALE (scale));

  priv = GET_PRIVATE (scale);

  if (channel != priv->channel)
    {
      priv->channel = channel;

      priv->needs_render = TRUE;
      gtk_widget_queue_draw (GTK_WIDGET (scale));

      g_object_notify (G_OBJECT (scale), "channel");
    }
}

/**
 * gimp_color_scale_set_color:
 * @scale: a #GimpColorScale widget
 * @color: the new color.
 *
 * Changes the color value of the @scale.
 **/
void
gimp_color_scale_set_color (GimpColorScale *scale,
                            GeglColor      *color)
{
  GimpColorScalePrivate *priv;
  GeglColor             *old_color;

  g_return_if_fail (GIMP_IS_COLOR_SCALE (scale));
  g_return_if_fail (GEGL_IS_COLOR (color));

  priv = GET_PRIVATE (scale);

  old_color = priv->color;
  priv->color = gegl_color_duplicate (color);

  if (! gimp_color_is_perceptually_identical (old_color, priv->color))
    {
      priv->needs_render = TRUE;
      gtk_widget_queue_draw (GTK_WIDGET (scale));
    }

  g_object_unref (old_color);
}

/**
 * gimp_color_scale_set_color_config:
 * @scale:  a #GimpColorScale widget.
 * @config: a #GimpColorConfig object.
 *
 * Sets the color management configuration to use with this color scale.
 *
 * Since: 2.10
 */
void
gimp_color_scale_set_color_config (GimpColorScale  *scale,
                                   GimpColorConfig *config)
{
  GimpColorScalePrivate *priv;

  g_return_if_fail (GIMP_IS_COLOR_SCALE (scale));
  g_return_if_fail (config == NULL || GIMP_IS_COLOR_CONFIG (config));

  priv = GET_PRIVATE (scale);

  if (config != priv->config)
    {
      if (priv->config)
        {
          g_signal_handlers_disconnect_by_func (priv->config,
                                                gimp_color_scale_notify_config,
                                                scale);
        }

      g_set_object (&priv->config, config);

      if (priv->config)
        {
          g_signal_connect (priv->config, "notify",
                            G_CALLBACK (gimp_color_scale_notify_config),
                            scale);

          gimp_color_scale_notify_config (priv->config, NULL, scale);
        }
    }
}


/* as in gtkrange.c */
static gboolean
should_invert (GtkRange *range)
{
  gboolean inverted  = gtk_range_get_inverted (range);
  gboolean flippable = gtk_range_get_flippable (range);

  if (gtk_orientable_get_orientation (GTK_ORIENTABLE (range)) ==
      GTK_ORIENTATION_HORIZONTAL)
    {
      return
        (inverted && !flippable) ||
        (inverted && flippable &&
         gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_LTR) ||
        (!inverted && flippable &&
         gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_RTL);
    }
  else
    {
      return inverted;
    }
}

static void
gimp_color_scale_render (GimpColorScale *scale)
{
  GimpColorScalePrivate *priv  = GET_PRIVATE (scale);
  GtkRange              *range = GTK_RANGE (scale);
  gdouble                rgb[4];
  gfloat                 hsv[3];
  gdouble                lch[3];
  gint                   multiplier = 1;
  guint                  x, y;
  gdouble               *channel_value   = NULL;
  gfloat                *channel_value_f = NULL;
  gboolean               from_hsv        = FALSE;
  gboolean               from_lch        = FALSE;
  gboolean               invert;
  guchar                *buf;
  guchar                *d;

  if ((buf = priv->buf) == NULL)
    return;

  if (priv->channel == GIMP_COLOR_SELECTOR_ALPHA)
    {
      gimp_color_scale_render_alpha (scale);
      return;
    }

  gegl_color_get_pixel (priv->color, babl_format_with_space ("R'G'B'A double", priv->format), rgb);
  gegl_color_get_pixel (priv->color, babl_format_with_space ("HSV float", priv->format), hsv);
  gegl_color_get_pixel (priv->color, babl_format ("CIE LCH(ab) double"), lch);

  switch (priv->channel)
    {
    case GIMP_COLOR_SELECTOR_HUE:        channel_value_f = &hsv[0]; break;
    case GIMP_COLOR_SELECTOR_SATURATION: channel_value_f = &hsv[1]; break;
    case GIMP_COLOR_SELECTOR_VALUE:      channel_value_f = &hsv[2]; break;

    case GIMP_COLOR_SELECTOR_RED:        channel_value = &rgb[0]; break;
    case GIMP_COLOR_SELECTOR_GREEN:      channel_value = &rgb[1]; break;
    case GIMP_COLOR_SELECTOR_BLUE:       channel_value = &rgb[2]; break;
    case GIMP_COLOR_SELECTOR_ALPHA:      channel_value = &rgb[3]; break;

    case GIMP_COLOR_SELECTOR_LCH_LIGHTNESS: channel_value = &lch[0]; break;
    case GIMP_COLOR_SELECTOR_LCH_CHROMA:    channel_value = &lch[1]; break;
    case GIMP_COLOR_SELECTOR_LCH_HUE:       channel_value = &lch[2]; break;
    }

  switch (priv->channel)
    {
    case GIMP_COLOR_SELECTOR_HUE:
    case GIMP_COLOR_SELECTOR_SATURATION:
    case GIMP_COLOR_SELECTOR_VALUE:
      from_hsv = TRUE;
      break;

    case GIMP_COLOR_SELECTOR_LCH_LIGHTNESS:
      multiplier = 100;
      from_lch = TRUE;
      break;
    case GIMP_COLOR_SELECTOR_LCH_CHROMA:
      multiplier = 200;
      from_lch = TRUE;
      break;
    case GIMP_COLOR_SELECTOR_LCH_HUE:
      multiplier = 360;
      from_lch = TRUE;
      break;

    default:
      break;
    }

  invert = should_invert (range);

  switch (gtk_orientable_get_orientation (GTK_ORIENTABLE (range)))
    {
    case GTK_ORIENTATION_HORIZONTAL:
      for (x = 0, d = buf; x < priv->width; x++, d += 3)
        {
          gdouble value = (gdouble) x * multiplier / (gdouble) (priv->width - 1);

          if (invert)
            value = multiplier - value;

          if (channel_value)
            *channel_value = value;
          else if (channel_value_f)
            *channel_value_f = (gfloat) value;

          if (from_hsv)
            babl_process (fish_hsv_to_rgb, &hsv, &rgb, 1);
          else if (from_lch)
            babl_process (fish_lch_to_rgb, &lch, &rgb, 1);

          /* This is only checking if a color is within the sRGB gamut. I want
           * to check compared to the image's space (anySpace) or softproof
           * space. TODO.
           */
          if (rgb[0] < 0.0 || rgb[0] > 1.0 ||
              rgb[1] < 0.0 || rgb[1] > 1.0 ||
              rgb[2] < 0.0 || rgb[2] > 1.0)
            {
              d[0] = priv->oog_color[0];
              d[1] = priv->oog_color[1];
              d[2] = priv->oog_color[2];
            }
          else
            {
              d[0] = rgb[0] * 255;
              d[1] = rgb[1] * 255;
              d[2] = rgb[2] * 255;
            }
        }

      d = buf + priv->width * 3;
      for (y = 1; y < priv->height; y++)
        {
          memcpy (d, buf, priv->width * 3);
          d += priv->width * 3;
        }
      break;

    case GTK_ORIENTATION_VERTICAL:
      for (y = 0; y < priv->height; y++)
        {
          gdouble value = (gdouble) y * multiplier / (gdouble) (priv->height - 1);
          guchar  u8rgb[3];

          if (invert)
            value = multiplier - value;

          if (channel_value)
            *channel_value = value;
          else if (channel_value_f)
            *channel_value_f = (gfloat) value;

          if (from_hsv)
            babl_process (fish_hsv_to_rgb, &hsv, &rgb, 1);
          else if (from_lch)
            babl_process (fish_lch_to_rgb, &lch, &rgb, 1);

          if (rgb[0] < 0.0 || rgb[0] > 1.0 ||
              rgb[1] < 0.0 || rgb[1] > 1.0 ||
              rgb[2] < 0.0 || rgb[2] > 1.0)
            {
              u8rgb[0] = priv->oog_color[0];
              u8rgb[1] = priv->oog_color[1];
              u8rgb[2] = priv->oog_color[2];
            }
          else
            {
              u8rgb[0] = rgb[0] * 255;
              u8rgb[1] = rgb[1] * 255;
              u8rgb[2] = rgb[2] * 255;
            }

          for (x = 0, d = buf; x < priv->width; x++, d += 3)
            {
              d[0] = u8rgb[0];
              d[1] = u8rgb[1];
              d[2] = u8rgb[2];
            }

          buf += priv->width * 3;
        }
      break;
    }
}

static void
gimp_color_scale_render_alpha (GimpColorScale *scale)
{
  GimpColorScalePrivate *priv  = GET_PRIVATE (scale);
  GtkRange              *range = GTK_RANGE (scale);
  gdouble                rgb[3];
  gboolean               invert;
  gdouble                a;
  guint                  x, y;
  guchar                *buf;
  guchar                *d, *l;

  invert = should_invert (range);

  buf = priv->buf;
  gegl_color_get_pixel (priv->color, babl_format_with_space ("R'G'B' double", priv->format), rgb);

  switch (gtk_orientable_get_orientation (GTK_ORIENTABLE (range)))
    {
    case GTK_ORIENTATION_HORIZONTAL:
      {
        guchar  *light;
        guchar  *dark;

        light = buf;
        /* this won't work correctly for very thin scales */
        dark  = (priv->height > GIMP_CHECK_SIZE_SM ?
                 buf + GIMP_CHECK_SIZE_SM * 3 * priv->width : light);

        for (x = 0, d = light, l = dark; x < priv->width; x++)
          {
            if ((x % GIMP_CHECK_SIZE_SM) == 0)
              {
                guchar *t;

                t = d;
                d = l;
                l = t;
              }

            a = (gdouble) x / (gdouble) (priv->width - 1);

            if (invert)
              a = 1.0 - a;

            l[0] = (GIMP_CHECK_LIGHT + (rgb[0] - GIMP_CHECK_LIGHT) * a) * 255.999;
            l[1] = (GIMP_CHECK_LIGHT + (rgb[1] - GIMP_CHECK_LIGHT) * a) * 255.999;
            l[2] = (GIMP_CHECK_LIGHT + (rgb[2] - GIMP_CHECK_LIGHT) * a) * 255.999;
            l += 3;

            d[0] = (GIMP_CHECK_DARK + (rgb[0] - GIMP_CHECK_DARK) * a) * 255.999;
            d[1] = (GIMP_CHECK_DARK + (rgb[1] - GIMP_CHECK_DARK) * a) * 255.999;
            d[2] = (GIMP_CHECK_DARK + (rgb[2] - GIMP_CHECK_DARK) * a) * 255.999;
            d += 3;
          }

        for (y = 0, d = buf; y < priv->height; y++, d += 3 * priv->width)
          {
            if (y == 0 || y == GIMP_CHECK_SIZE_SM)
              continue;

            if ((y / GIMP_CHECK_SIZE_SM) & 1)
              memcpy (d, dark, 3 * priv->width);
            else
              memcpy (d, light, 3 * priv->width);
          }
      }
      break;

    case GTK_ORIENTATION_VERTICAL:
      {
        guchar  light[3] = {0xff, 0xff, 0xff};
        guchar  dark[3]  = {0xff, 0xff, 0xff};

        for (y = 0, d = buf; y < priv->height; y++, d += priv->width * 3)
          {
            a = (gdouble) y / (gdouble) (priv->height - 1);

            if (invert)
              a = 1.0 - a;

            light[0] = (GIMP_CHECK_LIGHT + (rgb[0] - GIMP_CHECK_LIGHT) * a) * 255.999;
            light[1] = (GIMP_CHECK_LIGHT + (rgb[1] - GIMP_CHECK_LIGHT) * a) * 255.999;
            light[2] = (GIMP_CHECK_LIGHT + (rgb[2] - GIMP_CHECK_LIGHT) * a) * 255.999;

            dark[0] = (GIMP_CHECK_DARK + (rgb[0] - GIMP_CHECK_DARK) * a) * 255.999;
            dark[1] = (GIMP_CHECK_DARK + (rgb[1] - GIMP_CHECK_DARK) * a) * 255.999;
            dark[2] = (GIMP_CHECK_DARK + (rgb[2] - GIMP_CHECK_DARK) * a) * 255.999;

            for (x = 0, l = d; x < priv->width; x++, l += 3)
              {
                if (((x / GIMP_CHECK_SIZE_SM) ^ (y / GIMP_CHECK_SIZE_SM)) & 1)
                  {
                    l[0] = light[0];
                    l[1] = light[1];
                    l[2] = light[2];
                  }
                else
                  {
                    l[0] = dark[0];
                    l[1] = dark[1];
                    l[2] = dark[2];
                  }
              }
          }
      }
      break;
    }
}

static void
gimp_color_scale_notify_config (GimpColorConfig  *config,
                                const GParamSpec *pspec,
                                GimpColorScale   *scale)
{
  GimpColorScalePrivate *priv = GET_PRIVATE (scale);
  GeglColor             *color;

  color = gimp_color_config_get_out_of_gamut_color (config);
  /* TODO: shouldn't this be color-managed too, using the target space into
   * consideration?
   */
  gegl_color_get_pixel (color, babl_format ("R'G'B' u8"), priv->oog_color);
  priv->needs_render = TRUE;

  g_object_unref (color);
}
