color-to-alpha.c 13.3 KB
Newer Older
Marc Lehmann's avatar
Marc Lehmann committed
1 2 3 4 5
/*
 * Color To Alpha plug-in v1.0 by Seth Burgess, sjburges@gimp.org 1999/05/14
 *  with algorithm by clahey
 */

6
/* GIMP - The GNU Image Manipulation Program
Marc Lehmann's avatar
Marc Lehmann committed
7 8
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
9
 * This program is free software: you can redistribute it and/or modify
Marc Lehmann's avatar
Marc Lehmann committed
10
 * it under the terms of the GNU General Public License as published by
11
 * the Free Software Foundation; either version 3 of the License, or
Marc Lehmann's avatar
Marc Lehmann committed
12 13 14 15 16 17 18 19
 * (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
20
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
Marc Lehmann's avatar
Marc Lehmann committed
21 22
 */

Sven Neumann's avatar
Sven Neumann committed
23
#include "config.h"
24

25 26
#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>
27

28
#include "libgimp/stdplugins-intl.h"
Marc Lehmann's avatar
Marc Lehmann committed
29

30

31
#define PLUG_IN_PROC   "plug-in-colortoalpha"
32
#define PLUG_IN_BINARY "color-to-aplha"
33
#define PLUG_IN_ROLE   "gimp-color-to-aplha"
34 35
#define PRV_WIDTH      40
#define PRV_HEIGHT     20
Marc Lehmann's avatar
Marc Lehmann committed
36

37

38 39
typedef struct
{
Sven Neumann's avatar
Sven Neumann committed
40
  GimpRGB  color;
Marc Lehmann's avatar
Marc Lehmann committed
41 42
} C2AValues;

43

Marc Lehmann's avatar
Marc Lehmann committed
44 45
/* Declare local functions.
 */
David Odin's avatar
David Odin committed
46 47 48 49 50 51 52
static void        query                  (void);
static void        run                    (const gchar       *name,
                                           gint               nparams,
                                           const GimpParam   *param,
                                           gint              *nreturn_vals,
                                           GimpParam        **return_vals);

53
static inline void color_to_alpha         (GimpRGB           *src,
David Odin's avatar
David Odin committed
54 55 56 57 58
                                           const GimpRGB     *color);
static void        to_alpha_func          (const guchar      *src,
                                           guchar            *dest,
                                           gint               bpp,
                                           gpointer           data);
59

Marc Lehmann's avatar
Marc Lehmann committed
60
/* UI stuff */
David Odin's avatar
David Odin committed
61 62 63
static gboolean    color_to_alpha_dialog  (GimpDrawable      *drawable);
static void        color_to_alpha_preview (GimpPreview       *preview,
                                           GimpDrawable      *drawable);
Sven Neumann's avatar
Sven Neumann committed
64

65

66
const GimpPlugInInfo PLUG_IN_INFO =
Marc Lehmann's avatar
Marc Lehmann committed
67
{
68 69 70 71
  NULL,  /* init_proc  */
  NULL,  /* quit_proc  */
  query, /* query_proc */
  run,   /* run_proc   */
Marc Lehmann's avatar
Marc Lehmann committed
72 73
};

74
static C2AValues pvals =
Marc Lehmann's avatar
Marc Lehmann committed
75
{
76
  { 1.0, 1.0, 1.0, 1.0 } /* white default */
Marc Lehmann's avatar
Marc Lehmann committed
77 78
};

79

Marc Lehmann's avatar
Marc Lehmann committed
80 81 82
MAIN ()

static void
83
query (void)
Marc Lehmann's avatar
Marc Lehmann committed
84
{
85
  static const GimpParamDef args[] =
Marc Lehmann's avatar
Marc Lehmann committed
86
  {
87
    { GIMP_PDB_INT32,    "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
88 89 90
    { GIMP_PDB_IMAGE,    "image",    "Input image (unused)" },
    { GIMP_PDB_DRAWABLE, "drawable", "Input drawable"       },
    { GIMP_PDB_COLOR,    "color",    "Color to remove"      }
Marc Lehmann's avatar
Marc Lehmann committed
91
  };
Sven Neumann's avatar
Sven Neumann committed
92

93
  gimp_install_procedure (PLUG_IN_PROC,
94
                          N_("Convert a specified color to transparency"),
David Odin's avatar
David Odin committed
95 96 97 98 99 100 101 102 103 104 105
                          "This replaces as much of a given color as possible "
                          "in each pixel with a corresponding amount of alpha, "
                          "then readjusts the color accordingly.",
                          "Seth Burgess",
                          "Seth Burgess <sjburges@gimp.org>",
                          "7th Aug 1999",
                          N_("Color to _Alpha..."),
                          "RGB*",
                          GIMP_PLUGIN,
                          G_N_ELEMENTS (args), 0,
                          args, NULL);
106

107
  gimp_plugin_menu_register (PLUG_IN_PROC,
108
                             "<Image>/Colors/Modify");
109
  gimp_plugin_menu_register (PLUG_IN_PROC,
110
                             "<Image>/Layer/Transparency/Modify");
Marc Lehmann's avatar
Marc Lehmann committed
111 112 113
}

static void
114 115 116 117 118
run (const gchar      *name,
     gint              nparams,
     const GimpParam  *param,
     gint             *nreturn_vals,
     GimpParam       **return_vals)
Marc Lehmann's avatar
Marc Lehmann committed
119
{
Sven Neumann's avatar
Sven Neumann committed
120 121 122
  static GimpParam   values[1];
  GimpDrawable      *drawable;
  GimpPDBStatusType  status = GIMP_PDB_SUCCESS;
123
  GimpRunMode        run_mode;
124
  gint32             image_ID;
Marc Lehmann's avatar
Marc Lehmann committed
125 126 127 128

  run_mode = param[0].data.d_int32;

  *nreturn_vals = 1;
129
  *return_vals  = values;
Marc Lehmann's avatar
Marc Lehmann committed
130

131
  INIT_I18N ();
Sven Neumann's avatar
Sven Neumann committed
132

133
  values[0].type          = GIMP_PDB_STATUS;
Marc Lehmann's avatar
Marc Lehmann committed
134 135 136
  values[0].data.d_status = status;

  image_ID = param[1].data.d_image;
137
  drawable = gimp_drawable_get (param[2].data.d_drawable);
Marc Lehmann's avatar
Marc Lehmann committed
138 139 140

  switch (run_mode)
    {
Sven Neumann's avatar
Sven Neumann committed
141
    case GIMP_RUN_INTERACTIVE:
142
      gimp_get_data (PLUG_IN_PROC, &pvals);
David Odin's avatar
David Odin committed
143 144 145 146 147
      if (! color_to_alpha_dialog (drawable))
        {
          gimp_drawable_detach (drawable);
          return;
        }
148
      break;
149

Sven Neumann's avatar
Sven Neumann committed
150
    case GIMP_RUN_NONINTERACTIVE:
151
      if (nparams != 4)
David Odin's avatar
David Odin committed
152
        status = GIMP_PDB_CALLING_ERROR;
153

Sven Neumann's avatar
Sven Neumann committed
154
      if (status == GIMP_PDB_SUCCESS)
David Odin's avatar
David Odin committed
155
        pvals.color = param[3].data.d_color;
156
      break;
Marc Lehmann's avatar
Marc Lehmann committed
157

Sven Neumann's avatar
Sven Neumann committed
158
    case GIMP_RUN_WITH_LAST_VALS:
159
      gimp_get_data (PLUG_IN_PROC, &pvals);
160
      break;
Sven Neumann's avatar
Sven Neumann committed
161

162 163
    default:
      break;
164
    }
Marc Lehmann's avatar
Marc Lehmann committed
165

166 167
  if (status == GIMP_PDB_SUCCESS &&
      gimp_drawable_is_rgb (drawable->drawable_id) &&
168
      gimp_item_is_layer (drawable->drawable_id))
Marc Lehmann's avatar
Marc Lehmann committed
169
    {
170
      gboolean lock_alpha;
171

172 173
      gimp_image_undo_group_start (image_ID);

174
      /*  Add alpha if not present */
175
      gimp_layer_add_alpha (drawable->drawable_id);
176 177

      /*  Reget the drawable, bpp might have changed  */
178
      drawable = gimp_drawable_get (drawable->drawable_id);
179

180 181 182
      /*  Unset 'Lock alpha'  */
      lock_alpha = gimp_layer_get_lock_alpha (drawable->drawable_id);
      gimp_layer_set_lock_alpha (drawable->drawable_id, FALSE);
183

184
      gimp_progress_init (_("Removing color"));
185 186
      gimp_rgn_iterate2 (drawable, 0 /* unused */, to_alpha_func, NULL);

187
      gimp_layer_set_lock_alpha (drawable->drawable_id, lock_alpha);
188 189 190 191 192

      gimp_image_undo_group_end (image_ID);

      if (run_mode != GIMP_RUN_NONINTERACTIVE)
        gimp_displays_flush ();
Marc Lehmann's avatar
Marc Lehmann committed
193
    }
194 195

  gimp_drawable_detach (drawable);
196

Sven Neumann's avatar
Sven Neumann committed
197
  if (run_mode == GIMP_RUN_INTERACTIVE)
198
    gimp_set_data (PLUG_IN_PROC, &pvals, sizeof (pvals));
Marc Lehmann's avatar
Marc Lehmann committed
199 200 201 202

  values[0].data.d_status = status;
}

203
static inline void
David Odin's avatar
David Odin committed
204 205
color_to_alpha (GimpRGB       *src,
                const GimpRGB *color)
Marc Lehmann's avatar
Marc Lehmann committed
206
{
Sven Neumann's avatar
Sven Neumann committed
207
  GimpRGB alpha;
208

Sven Neumann's avatar
Sven Neumann committed
209 210 211 212
  alpha.a = src->a;

  if (color->r < 0.0001)
    alpha.r = src->r;
213
  else if (src->r > color->r)
Sven Neumann's avatar
Sven Neumann committed
214 215 216 217 218 219 220
    alpha.r = (src->r - color->r) / (1.0 - color->r);
  else if (src->r < color->r)
    alpha.r = (color->r - src->r) / color->r;
  else alpha.r = 0.0;

  if (color->g < 0.0001)
    alpha.g = src->g;
221
  else if (src->g > color->g)
Sven Neumann's avatar
Sven Neumann committed
222
    alpha.g = (src->g - color->g) / (1.0 - color->g);
223
  else if (src->g < color->g)
Sven Neumann's avatar
Sven Neumann committed
224 225 226 227 228
    alpha.g = (color->g - src->g) / (color->g);
  else alpha.g = 0.0;

  if (color->b < 0.0001)
    alpha.b = src->b;
229
  else if (src->b > color->b)
Sven Neumann's avatar
Sven Neumann committed
230
    alpha.b = (src->b - color->b) / (1.0 - color->b);
231
  else if (src->b < color->b)
Sven Neumann's avatar
Sven Neumann committed
232 233 234
    alpha.b = (color->b - src->b) / (color->b);
  else alpha.b = 0.0;

235
  if (alpha.r > alpha.g)
Sven Neumann's avatar
Sven Neumann committed
236
    {
237
      if (alpha.r > alpha.b)
David Odin's avatar
David Odin committed
238 239 240
        {
          src->a = alpha.r;
        }
Sven Neumann's avatar
Sven Neumann committed
241
      else
David Odin's avatar
David Odin committed
242 243 244
        {
          src->a = alpha.b;
        }
Sven Neumann's avatar
Sven Neumann committed
245
    }
246
  else if (alpha.g > alpha.b)
Sven Neumann's avatar
Sven Neumann committed
247 248 249
    {
      src->a = alpha.g;
    }
Marc Lehmann's avatar
Marc Lehmann committed
250
  else
Sven Neumann's avatar
Sven Neumann committed
251 252 253
    {
      src->a = alpha.b;
    }
254

Sven Neumann's avatar
Sven Neumann committed
255
  if (src->a < 0.0001)
Marc Lehmann's avatar
Marc Lehmann committed
256 257
    return;

Sven Neumann's avatar
Sven Neumann committed
258 259 260 261 262
  src->r = (src->r - color->r) / src->a + color->r;
  src->g = (src->g - color->g) / src->a + color->g;
  src->b = (src->b - color->b) / src->a + color->b;

  src->a *= alpha.a;
Marc Lehmann's avatar
Marc Lehmann committed
263
}
Sven Neumann's avatar
Sven Neumann committed
264

Marc Lehmann's avatar
Marc Lehmann committed
265
/*
266 267 268
 * An excerpt from a discussion on #gimp that sheds some light on the ideas
 * behind the algorithm that is being used here.
 *
269 270 271
  <clahey>   so if a1 > c1, a2 > c2, and a3 > c2 and a1 - c1 > a2-c2, a3-c3,
             then a1 = b1 * alpha + c1 * (1-alpha)
             So, maximizing alpha without taking b1 above 1 gives
David Odin's avatar
David Odin committed
272
             a1 = alpha + c1(1-alpha) and therefore alpha = (a1-c1) / (1-c1).
273
  <sjburges> clahey: btw, the ordering of that a2, a3 in the white->alpha didn't
274
             matter
275 276
  <clahey>   sjburges: You mean that it could be either a1, a2, a3 or
             a1, a3, a2?
277 278
  <sjburges> yeah
  <sjburges> because neither one uses the other
279 280 281
  <clahey>   sjburges: That's exactly as it should be.  They are both just
             getting reduced to the same amount, limited by the the darkest
             color.
282
  <clahey>   Then a2 = b2 * alpha + c2 * (1- alpha).  Solving for b2 gives
283 284
             b2 = (a1-c2)/alpha + c2.
  <sjburges> yeah
285
  <clahey>   That gives us are formula for if the background is darker than the
286
             foreground? Yep.
287 288 289 290
  <clahey>   Next if a1 < c1, a2 < c2, a3 < c3, and c1-a1 > c2-a2, c3-a3, and
             by our desired result a1 = b1 * alpha + c1 * (1-alpha),
             we maximize alpha without taking b1 negative gives
             alpha = 1 - a1 / c1.
291
  <clahey>   And then again, b2 = (a2-c2) / alpha + c2 by the same formula.
292 293
             (Actually, I think we can use that formula for all cases, though
             it may possibly introduce rounding error.
294
  <clahey>   sjburges: I like the idea of using floats to avoid rounding error.
295
             Good call.
296
 */
Marc Lehmann's avatar
Marc Lehmann committed
297

298
static void
David Odin's avatar
David Odin committed
299
to_alpha_func (const guchar *src,
300 301 302
              guchar       *dest,
              gint          bpp,
              gpointer      data)
Marc Lehmann's avatar
Marc Lehmann committed
303
{
304
  GimpRGB color;
305

David Odin's avatar
David Odin committed
306 307 308 309
  if (bpp == 3)
    gimp_rgba_set_uchar (&color, src[0], src[1], src[2], 255);
  else
    gimp_rgba_set_uchar (&color, src[0], src[1], src[2], src[3]);
310

David Odin's avatar
David Odin committed
311
  color_to_alpha (&color, &pvals.color);
312
  gimp_rgba_get_uchar (&color, &dest[0], &dest[1], &dest[2], &dest[3]);
Marc Lehmann's avatar
Marc Lehmann committed
313 314
}

David Odin's avatar
David Odin committed
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
static void
color_to_alpha_preview (GimpPreview  *preview,
                        GimpDrawable *drawable)
{
  GimpPixelRgn  src_rgn;
  gint          x, y;
  gint          width, height;
  gint          bpp;
  gint          i;
  guchar       *src, *dest;

  bpp = drawable->bpp;
  gimp_preview_get_position (preview, &x, &y);
  gimp_preview_get_size (preview, &width, &height);

  dest = g_new (guchar, width * height * 4);
331
  src = g_new (guchar, width * height * bpp);
David Odin's avatar
David Odin committed
332 333 334 335 336 337 338 339 340 341

  gimp_pixel_rgn_init (&src_rgn, drawable,
                       x, y, width, height,
                       FALSE, FALSE);
  gimp_pixel_rgn_get_rect (&src_rgn, src, x, y, width, height);

  for (i = 0; i < width * height; i++)
    to_alpha_func (src + i * bpp, dest + i * 4, bpp, NULL);

  g_free (src);
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360

  /* Our code assumes that the drawable has an alpha channel (and adds
   * one later if the effect is actually performed). For that reason
   * we have to take care when drawing the preview.
   */
  if (bpp == 4)
    {
      gimp_preview_draw_buffer (preview, dest, width * 4);
    }
  else
    {
      /* This is not correct because we ignore the selection, but it
       * is the best we can easily do.
       */
      gimp_preview_area_draw (GIMP_PREVIEW_AREA (preview->area),
                              0, 0, width, height,
                              GIMP_RGBA_IMAGE, dest, width * 4);
    }

David Odin's avatar
David Odin committed
361
  g_free (dest);
Marc Lehmann's avatar
Marc Lehmann committed
362 363
}

Sven Neumann's avatar
Sven Neumann committed
364
static gboolean
David Odin's avatar
David Odin committed
365
color_to_alpha_dialog (GimpDrawable *drawable)
Marc Lehmann's avatar
Marc Lehmann committed
366
{
David Odin's avatar
David Odin committed
367 368 369
  GtkWidget *dialog;
  GtkWidget *main_vbox;
  GtkWidget *preview;
370
  GtkWidget *hbox;
371
  GtkWidget *button;
Marc Lehmann's avatar
Marc Lehmann committed
372
  GtkWidget *label;
373
  gboolean   run;
Marc Lehmann's avatar
Marc Lehmann committed
374

375
  gimp_ui_init (PLUG_IN_BINARY, TRUE);
Marc Lehmann's avatar
Marc Lehmann committed
376

377
  dialog = gimp_dialog_new (_("Color to Alpha"), PLUG_IN_ROLE,
David Odin's avatar
David Odin committed
378
                            NULL, 0,
379
                            gimp_standard_help_func, PLUG_IN_PROC,
380

David Odin's avatar
David Odin committed
381 382
                            GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
                            GTK_STOCK_OK,     GTK_RESPONSE_OK,
Sven Neumann's avatar
Sven Neumann committed
383

David Odin's avatar
David Odin committed
384
                            NULL);
385

386
  gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
387 388 389
                                           GTK_RESPONSE_OK,
                                           GTK_RESPONSE_CANCEL,
                                           -1);
390

391
  gimp_window_set_transient (GTK_WINDOW (dialog));
392

393
  main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
David Odin's avatar
David Odin committed
394
  gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12);
395 396
  gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
                      main_vbox, TRUE, TRUE, 0);
David Odin's avatar
David Odin committed
397
  gtk_widget_show (main_vbox);
398

399
  preview = gimp_drawable_preview_new (drawable, NULL);
400
  gtk_box_pack_start (GTK_BOX (main_vbox), preview, TRUE, TRUE, 0);
David Odin's avatar
David Odin committed
401
  gtk_widget_show (preview);
402

David Odin's avatar
David Odin committed
403 404 405 406
  g_signal_connect (preview, "invalidated",
                    G_CALLBACK (color_to_alpha_preview),
                    drawable);

407
  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
David Odin's avatar
David Odin committed
408
  gtk_box_pack_start (GTK_BOX (main_vbox), hbox, FALSE, FALSE, 0);
409
  gtk_widget_show (hbox);
410

411
  label = gtk_label_new (C_("color-to-alpha", "From:"));
412
  gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
413
  gtk_widget_show (label);
414

415
  button = gimp_color_button_new (_("Color to Alpha Color Picker"),
David Odin's avatar
David Odin committed
416 417 418
                                  PRV_WIDTH, PRV_HEIGHT,
                                  &pvals.color,
                                  GIMP_COLOR_AREA_FLAT);
419
  gimp_color_button_set_update (GIMP_COLOR_BUTTON (button), TRUE);
420
  gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
421
  gtk_widget_show (button);
Marc Lehmann's avatar
Marc Lehmann committed
422

423
  g_signal_connect (button, "color-changed",
424 425
                    G_CALLBACK (gimp_color_button_get_color),
                    &pvals.color);
426
  g_signal_connect_swapped (button, "color-changed",
David Odin's avatar
David Odin committed
427 428
                            G_CALLBACK (gimp_preview_invalidate),
                            preview);
429

430
  label = gtk_label_new (_("to alpha"));
431
  gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
432
  gtk_widget_show (label);
Marc Lehmann's avatar
Marc Lehmann committed
433

David Odin's avatar
David Odin committed
434
  gtk_widget_show (dialog);
Marc Lehmann's avatar
Marc Lehmann committed
435

David Odin's avatar
David Odin committed
436
  run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
Marc Lehmann's avatar
Marc Lehmann committed
437

David Odin's avatar
David Odin committed
438
  gtk_widget_destroy (dialog);
439

440
  return run;
Marc Lehmann's avatar
Marc Lehmann committed
441
}