static gboolean
gimp_hue_saturation_config_copy (GimpConfig   *src,
                                 GimpConfig   *dest,
                                 GParamFlags   flags)
{
  GimpHueSaturationConfig *src_config  = GIMP_HUE_SATURATION_CONFIG (src);
  GimpHueSaturationConfig *dest_config = GIMP_HUE_SATURATION_CONFIG (dest);
  GimpHueRange             range;

  for (range = GIMP_HUE_RANGE_ALL; range <= GIMP_HUE_RANGE_MAGENTA; range++)
    {
      dest_config->hue[range]        = src_config->hue[range];
      dest_config->saturation[range] = src_config->saturation[range];
      dest_config->lightness[range]  = src_config->lightness[range];
    }

  g_object_notify (G_OBJECT (dest), "hue");
  g_object_notify (G_OBJECT (dest), "saturation");
  g_object_notify (G_OBJECT (dest), "lightness");

  dest_config->range   = src_config->range;
  dest_config->overlap = src_config->overlap;

  g_object_notify (G_OBJECT (dest), "range");
  g_object_notify (G_OBJECT (dest), "overlap");

  return TRUE;
}
static void
gimp_hue_saturation_config_get_property (GObject    *object,
                                         guint       property_id,
                                         GValue     *value,
                                         GParamSpec *pspec)
{
  GimpHueSaturationConfig *self = GIMP_HUE_SATURATION_CONFIG (object);

  switch (property_id)
    {
    case PROP_RANGE:
      g_value_set_enum (value, self->range);
      break;

    case PROP_HUE:
      g_value_set_double (value, self->hue[self->range]);
      break;

    case PROP_SATURATION:
      g_value_set_double (value, self->saturation[self->range]);
      break;

    case PROP_LIGHTNESS:
      g_value_set_double (value, self->lightness[self->range]);
      break;

    case PROP_OVERLAP:
      g_value_set_double (value, self->overlap);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}
static gboolean
gimp_hue_saturation_config_equal (GimpConfig *a,
                                  GimpConfig *b)
{
  GimpHueSaturationConfig *config_a = GIMP_HUE_SATURATION_CONFIG (a);
  GimpHueSaturationConfig *config_b = GIMP_HUE_SATURATION_CONFIG (b);
  GimpHueRange             range;

  for (range = GIMP_HUE_RANGE_ALL; range <= GIMP_HUE_RANGE_MAGENTA; range++)
    {
      if (config_a->hue[range]        != config_b->hue[range]        ||
          config_a->saturation[range] != config_b->saturation[range] ||
          config_a->lightness[range]  != config_b->lightness[range])
        return FALSE;
    }

  /* don't compare "range" */

  if (config_a->overlap != config_b->overlap)
    return FALSE;

  return TRUE;
}
static void
gimp_hue_saturation_config_reset (GimpConfig *config)
{
  GimpHueSaturationConfig *hs_config = GIMP_HUE_SATURATION_CONFIG (config);
  GimpHueRange             range;

  for (range = GIMP_HUE_RANGE_ALL; range <= GIMP_HUE_RANGE_MAGENTA; range++)
    {
      hs_config->range = range;
      gimp_hue_saturation_config_reset_range (hs_config);
    }

  gimp_config_reset_property (G_OBJECT (config), "range");
  gimp_config_reset_property (G_OBJECT (config), "overlap");
}
static gboolean
gimp_hue_saturation_config_deserialize (GimpConfig *config,
                                        GScanner   *scanner,
                                        gint        nest_level,
                                        gpointer    data)
{
  GimpHueSaturationConfig *hs_config = GIMP_HUE_SATURATION_CONFIG (config);
  GimpHueRange             old_range;
  gboolean                 success = TRUE;

  old_range = hs_config->range;

  success = gimp_config_deserialize_properties (config, scanner, nest_level);

  g_object_set (config, "range", old_range, NULL);

  return success;
}
static gboolean
gimp_hue_saturation_config_serialize (GimpConfig       *config,
                                      GimpConfigWriter *writer,
                                      gpointer          data)
{
  GimpHueSaturationConfig *hs_config = GIMP_HUE_SATURATION_CONFIG (config);
  GimpHueRange             range;
  GimpHueRange             old_range;
  gboolean                 success = TRUE;

  if (! gimp_config_serialize_property_by_name (config, "time", writer))
    return FALSE;

  old_range = hs_config->range;

  for (range = GIMP_HUE_RANGE_ALL; range <= GIMP_HUE_RANGE_MAGENTA; range++)
    {
      hs_config->range = range;

      success = (gimp_config_serialize_property_by_name (config, "range",
                                                         writer) &&
                 gimp_config_serialize_property_by_name (config, "hue",
                                                         writer) &&
                 gimp_config_serialize_property_by_name (config, "saturation",
                                                         writer) &&
                 gimp_config_serialize_property_by_name (config, "lightness",
                                                         writer));

      if (! success)
        break;
    }

  if (success)
    success = gimp_config_serialize_property_by_name (config, "overlap",
                                                      writer);

  hs_config->range = old_range;

  return success;
}
static void
hue_saturation_config_notify (GObject               *object,
                              GParamSpec            *pspec,
                              GimpHueSaturationTool *hs_tool)
{
  GimpHueSaturationConfig *config = GIMP_HUE_SATURATION_CONFIG (object);

  if (! hs_tool->hue_data)
    return;

  if (! strcmp (pspec->name, "range"))
    {
      gimp_int_radio_group_set_active (GTK_RADIO_BUTTON (hs_tool->range_radio),
                                       config->range);
    }
  else if (! strcmp (pspec->name, "hue"))
    {
      gtk_adjustment_set_value (hs_tool->hue_data,
                                config->hue[config->range] * 180.0);
    }
  else if (! strcmp (pspec->name, "lightness"))
    {
      gtk_adjustment_set_value (hs_tool->lightness_data,
                                config->lightness[config->range] * 100.0);
    }
  else if (! strcmp (pspec->name, "saturation"))
    {
      gtk_adjustment_set_value (hs_tool->saturation_data,
                                config->saturation[config->range] * 100.0);
    }
  else if (! strcmp (pspec->name, "overlap"))
    {
      gtk_adjustment_set_value (hs_tool->overlap_data,
                                config->overlap * 100.0);
    }

  hue_saturation_update_color_areas (hs_tool);

  gimp_image_map_tool_preview (GIMP_IMAGE_MAP_TOOL (hs_tool));
}
static gboolean
gimp_operation_hue_saturation_process (GeglOperation       *operation,
                                       void                *in_buf,
                                       void                *out_buf,
                                       glong                samples,
                                       const GeglRectangle *roi)
{
  GimpOperationPointFilter *point  = GIMP_OPERATION_POINT_FILTER (operation);
  GimpHueSaturationConfig  *config = GIMP_HUE_SATURATION_CONFIG (point->config);
  gfloat                   *src    = in_buf;
  gfloat                   *dest   = out_buf;
  gfloat                    overlap;

  if (! config)
    return FALSE;

  overlap = config->overlap / 2.0;

  while (samples--)
    {
      GimpRGB  rgb;
      GimpHSL  hsl;
      gdouble  h;
      gint     hue_counter;
      gint     hue                 = 0;
      gint     secondary_hue       = 0;
      gboolean use_secondary_hue   = FALSE;
      gfloat   primary_intensity   = 0.0;
      gfloat   secondary_intensity = 0.0;

      rgb.r = src[RED];
      rgb.g = src[GREEN];
      rgb.b = src[BLUE];

      gimp_rgb_to_hsl (&rgb, &hsl);

      h = hsl.h * 6.0;

      for (hue_counter = 0; hue_counter < 7; hue_counter++)
        {
          gdouble hue_threshold = (gdouble) hue_counter + 0.5;

          if (h < ((gdouble) hue_threshold + overlap))
            {
              hue = hue_counter;

              if (overlap > 0.0 && h > ((gdouble) hue_threshold - overlap))
                {
                  use_secondary_hue = TRUE;

                  secondary_hue = hue_counter + 1;

                  secondary_intensity =
                    (h - (gdouble) hue_threshold + overlap) / (2.0 * overlap);

                  primary_intensity = 1.0 - secondary_intensity;
                }
              else
                {
                  use_secondary_hue = FALSE;
                }

              break;
            }
        }

      if (hue >= 6)
        {
          hue = 0;
          use_secondary_hue = FALSE;
        }

      if (secondary_hue >= 6)
        {
          secondary_hue = 0;
        }

      /*  transform into GimpHueRange values  */
      hue++;
      secondary_hue++;

      if (use_secondary_hue)
        {
          gdouble mapped_primary_hue;
          gdouble mapped_secondary_hue;
          gdouble diff;

          mapped_primary_hue   = map_hue (config, hue,           hsl.h);
          mapped_secondary_hue = map_hue (config, secondary_hue, hsl.h);

          /* Find nearest hue on the circle between primary and
           * secondary hue
           */
          diff = mapped_primary_hue - mapped_secondary_hue;
          if (diff < -0.5)
            {
              mapped_secondary_hue -= 1.0;
            }
          else if (diff >= 0.5)
            {
              mapped_secondary_hue += 1.0;
            }

          hsl.h = (mapped_primary_hue   * primary_intensity +
                   mapped_secondary_hue * secondary_intensity);

          hsl.s = (map_saturation (config, hue,           hsl.s) * primary_intensity +
                   map_saturation (config, secondary_hue, hsl.s) * secondary_intensity);

          hsl.l = (map_lightness (config, hue,           hsl.l) * primary_intensity +
                   map_lightness (config, secondary_hue, hsl.l) * secondary_intensity);
        }
      else
        {
          hsl.h = map_hue        (config, hue, hsl.h);
          hsl.s = map_saturation (config, hue, hsl.s);
          hsl.l = map_lightness  (config, hue, hsl.l);
        }

      gimp_hsl_to_rgb (&hsl, &rgb);

      dest[RED]   = rgb.r;
      dest[GREEN] = rgb.g;
      dest[BLUE]  = rgb.b;
      dest[ALPHA] = src[ALPHA];

      src  += 4;
      dest += 4;
    }

  return TRUE;
}