color-rotate.c 9.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13
/* This file is an image processing operation for GEGL
 *
 * GEGL 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.
 *
 * GEGL 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
14
 * License along with GEGL; if not, see <https://www.gnu.org/licenses/>.
15 16 17 18 19 20 21 22 23 24
 *
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * This is a plug-in for GIMP.
 *
 * Colormap-Rotation plug-in. Exchanges two color ranges.
 *
 * Copyright (C) 1999 Sven Anders (anderss@fmi.uni-passau.de)
 *                    Based on code from Pavel Grinfeld (pavel@ml.com)
 * Copyright (C) 2011 Robert Sasu <sasu.robert@gmail.com>
25
 * Copyright (C) 2011 Mukund Sivaraman <muks@banu.com>
26 27 28 29 30
 */

#include "config.h"
#include <glib/gi18n-lib.h>

31 32
#ifdef GEGL_PROPERTIES

33 34 35 36 37 38 39
enum_start (gegl_color_rotate_gray_mode)
  enum_value (GEGL_COLOR_ROTATE_GRAY_TREAT_AS,  "treat-as",
              N_("Treat as this"))
  enum_value (GEGL_COLOR_ROTATE_GRAY_CHANGE_TO, "change-to",
              N_("Change to this"))
enum_end (GeglColorRotateGrayMode)

40
property_boolean (src_clockwise, _("Clockwise"), FALSE)
41 42
    description (_("Switch to clockwise"))

43 44
property_double (src_from, _("From"), 0.0)
    description (_("Start angle of the source color range"))
45
    value_range (0.0, 360.0)
46 47
    ui_meta     ("unit", "degree")

48
property_double (src_to, _("To"), 90.0)
49
    description (_("End angle of the source color range"))
50
    value_range (0.0, 360.0)
51 52
    ui_meta     ("unit", "degree")

53
property_boolean (dest_clockwise, _("Clockwise"), FALSE)
54 55
    description (_("Switch to clockwise"))

56
property_double (dest_from, _("From"), 0.0)
Marco Ciampa's avatar
Marco Ciampa committed
57
    description (_("Start angle of the destination color range"))
58 59
    value_range (0.0, 360.0)
    ui_meta     ("unit", "degree")
60

61
property_double (dest_to, _("To"), 90.0)
62
    description (_("End angle of the destination color range"))
63 64
    value_range (0.0, 360.0)
    ui_meta     ("unit", "degree")
65

66 67 68 69 70 71 72 73 74 75 76 77
property_double (threshold, _("Gray threshold"), 0.0)
    description (_("Colors with a saturation less than this will treated "
                   "as gray"))
    value_range (0.0, 1.0)

property_enum   (gray_mode, _("Gray mode"),
    GeglColorRotateGrayMode, gegl_color_rotate_gray_mode,
    GEGL_COLOR_ROTATE_GRAY_CHANGE_TO)
    description (_("Treat as this: Gray colors from above source range "
                   "will be treated as if they had this hue and saturation\n"
                   "Change to this: Change gray colors to this "
                   "hue and saturation"))
78 79

property_double (hue, _("Hue"), 0.0)
80
    description (_("Hue value for above gray settings"))
81 82
    value_range (0.0, 360.0)
    ui_meta     ("unit", "degree")
83 84

property_double (saturation, _("Saturation"), 0.0)
85
    description (_("Saturation value for above gray settings"))
86
    value_range (0.0, 1.0)
87 88 89

#else

90
#define GEGL_OP_POINT_FILTER
91
#define GEGL_OP_NAME     color_rotate
92
#define GEGL_OP_C_SOURCE color-rotate.c
93

94
#include "gegl-op.h"
95

96
#define TWO_PI        (2 * G_PI)
97 98
#define DEG_TO_RAD(d) (((d) * G_PI) / 180.0)

99 100
static void
prepare (GeglOperation *operation)
101
{
102
  const Babl *space = gegl_operation_get_source_space (operation, "input");
103 104 105
  /* gamma-corrected RGB because that's what the HSV conversion
   * functions expect
   */
106
  gegl_operation_set_format (operation, "input",
107
                             babl_format_with_space ("R~G~B~A float", space));
108
  gegl_operation_set_format (operation, "output",
109
                             babl_format_with_space ("R~G~B~A float", space));
110 111 112
}

static void
113 114 115 116 117 118
rgb_to_hsv (gfloat  r,
            gfloat  g,
            gfloat  b,
            gfloat *h,
            gfloat *s,
            gfloat *v)
119
{
120 121
  float min;
  float delta;
122

123 124 125
  *v = MAX (MAX (r, g), b);
  min = MIN (MIN (r, g), b);
  delta = *v - min;
126

127
  if (delta == 0.0f)
128
    {
129 130
      *h = 0.0f;
      *s = 0.0f;
131 132 133
    }
  else
    {
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
      *s = delta / *v;

      if (r == *v)
        {
          *h = (g - b) / delta;
          if (*h < 0.0f)
            *h += 6.0f;
        }
      else if (g == *v)
        {
          *h = 2.0f + (b - r) / delta;
        }
      else
        {
          *h = 4.0f + (r - g) / delta;
        }

      *h /= 6.0f;
152 153 154 155
    }
}

static void
156 157 158 159 160 161
hsv_to_rgb (gfloat  h,
            gfloat  s,
            gfloat  v,
            gfloat *r,
            gfloat *g,
            gfloat *b)
162
{
163
  if (s == 0.0)
164
    {
165 166 167
      *r = v;
      *g = v;
      *b = v;
168 169 170
    }
  else
    {
171 172 173 174 175
      int hi;
      float frac;
      float w, q, t;

      h *= 6.0;
176

177 178
      if (h >= 6.0)
        h -= 6.0;
179

180 181 182 183 184
      hi = (int) h;
      frac = h - hi;
      w = v * (1.0 - s);
      q = v * (1.0 - (s * frac));
      t = v * (1.0 - (s * (1.0 - frac)));
185

186
      switch (hi)
187 188
        {
        case 0:
189 190 191
          *r = v;
          *g = t;
          *b = w;
192 193
          break;
        case 1:
194 195 196
          *r = q;
          *g = v;
          *b = w;
197 198
          break;
        case 2:
199 200 201
          *r = w;
          *g = v;
          *b = t;
202 203
          break;
        case 3:
204 205 206
          *r = w;
          *g = q;
          *b = v;
207 208
          break;
        case 4:
209 210 211
          *r = t;
          *g = w;
          *b = v;
212 213
          break;
        case 5:
214 215 216
          *r = v;
          *g = w;
          *b = q;
217 218
          break;
        }
219 220 221 222 223 224 225
    }
}

static gfloat
angle_mod_2PI (gfloat angle)
{
  if (angle < 0)
226 227 228
    return angle + TWO_PI;
  else if (angle > TWO_PI)
    return angle - TWO_PI;
229 230 231 232 233 234
  else
    return angle;
}

static gfloat
angle_inside_slice (gfloat   hue,
235 236
                    gfloat   from,
                    gfloat   to,
237 238
                    gboolean cl)
{
239
  gint cw_ccw = cl ? -1 : 1;
240

241
  return angle_mod_2PI (cw_ccw * DEG_TO_RAD (to - hue)) /
242
         angle_mod_2PI (cw_ccw * DEG_TO_RAD (from - to));
243 244 245 246 247 248
}

static gboolean
is_gray (gfloat  s,
         gdouble threshold)
{
249
  return (s <= threshold);
250 251 252 253 254
}

static gfloat
linear (gfloat A,
        gfloat B,
255 256 257
        gfloat C,
        gfloat D,
        gfloat x)
258 259 260
{
  if (B > A)
    {
261 262 263 264
      if (A <= x && x <= B)
        return C + (D - C) / (B - A) * (x - A);
      else if (A <= x + TWO_PI && x + TWO_PI <= B)
        return C + (D - C) / (B - A) * (x + TWO_PI - A);
265 266 267 268 269
      else
        return x;
    }
  else
    {
270 271 272 273
      if (B <= x && x <= A)
        return C + (D - C) / (B - A) * (x - A);
      else if (B <= x + TWO_PI && x + TWO_PI <= A)
        return C + (D - C) / (B - A) * (x + TWO_PI - A);
274 275 276 277 278 279
      else
        return x;
    }
}

static gfloat
280 281
left_end (gfloat   from,
          gfloat   to,
282 283 284 285
          gboolean cl)
{
  gfloat alpha  = DEG_TO_RAD (from);
  gfloat beta   = DEG_TO_RAD (to);
286
  gint   cw_ccw = cl ? -1 : 1;
287 288 289

  switch (cw_ccw)
    {
290
    case -1:
291 292
      if (alpha < beta)
        return alpha + TWO_PI;
293 294 295 296 297 298 299

    default:
      return alpha; /* 1 */
    }
}

static gfloat
300 301
right_end (gfloat   from,
           gfloat   to,
302 303 304 305
           gboolean cl)
{
  gfloat alpha  = DEG_TO_RAD (from);
  gfloat beta   = DEG_TO_RAD (to);
306
  gint   cw_ccw = cl ? -1 : 1;
307 308 309 310

  switch (cw_ccw)
    {
    case 1:
311 312
      if (beta < alpha)
        return beta + TWO_PI;
313 314 315 316 317 318 319

    default:
      return beta; /* -1 */
    }
}

static void
320
color_rotate (GeglProperties *o,
321 322
              gfloat         *input,
              gfloat         *output)
323
{
324
  gfloat   h, s, v;
325
  gboolean skip = FALSE;
326

327
  rgb_to_hsv (input[0], input[1], input[2],
328
              &h, &s, &v);
329

330
  if (is_gray (s, o->threshold))
331
    {
332
      if (o->gray_mode == GEGL_COLOR_ROTATE_GRAY_TREAT_AS)
333
        {
334 335
          if (angle_inside_slice (o->hue, o->src_from, o->src_to,
                                  o->src_clockwise) <= 1)
336
            {
337
              h = DEG_TO_RAD (o->hue) / TWO_PI;
338
              s = o->saturation;
339 340 341 342 343 344
            }
          else
            {
              skip = TRUE;
            }
        }
345
      else
346 347
        {
          skip = TRUE;
348 349 350

          h = DEG_TO_RAD (o->hue) / TWO_PI;
          s = o->saturation;
351
        }
352 353
    }

354 355
  if (! skip)
    {
356 357 358 359
      h = linear (left_end (o->src_from, o->src_to, o->src_clockwise),
                  right_end (o->src_from, o->src_to, o->src_clockwise),
                  left_end (o->dest_from, o->dest_to, o->dest_clockwise),
                  right_end (o->dest_from, o->dest_to, o->dest_clockwise),
360
                  h * TWO_PI);
361

362
      h = angle_mod_2PI (h) / TWO_PI;
363
    }
364

365 366
  hsv_to_rgb (h, s, v,
              output, output + 1, output + 2);
367 368 369 370
}

static gboolean
process (GeglOperation       *operation,
371 372 373
         void                *in_buf,
         void                *out_buf,
         glong                samples,
374 375
         const GeglRectangle *result,
         gint                 level)
376
{
377
  GeglProperties *o      = GEGL_PROPERTIES (operation);
378 379
  gfloat         *input  = in_buf;
  gfloat         *output = out_buf;
380

381 382 383
  while (samples--)
    {
      color_rotate (o, input, output);
384

385
      output[3] = input[3];
386

387 388 389
      input  += 4;
      output += 4;
    }
390 391 392 393 394

  return  TRUE;
}

static void
395
gegl_op_class_init (GeglOpClass *klass)
396
{
397 398
  GeglOperationClass            *operation_class;
  GeglOperationPointFilterClass *filter_class;
399 400

  operation_class = GEGL_OPERATION_CLASS (klass);
401
  filter_class    = GEGL_OPERATION_POINT_FILTER_CLASS (klass);
402 403

  operation_class->prepare = prepare;
404
  filter_class->process    = process;
405

406
  gegl_operation_class_set_keys (operation_class,
407 408
    "categories",   "color",
    "name",         "gegl:color-rotate",
409
    "title",        _("Color Rotate"),
410
    "description",  _("Replace a range of colors with another"),
411
    NULL);
412 413 414
}

#endif