colortoalpha.c 12.6 KB
Newer Older
Marc Lehmann's avatar
Marc Lehmann committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
 * Color To Alpha plug-in v1.0 by Seth Burgess, sjburges@gimp.org 1999/05/14
 *  with algorithm by clahey
 */

/* The GIMP -- an 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 2 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

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

Marc Lehmann's avatar
Marc Lehmann committed
26
27
#include <stdio.h>
#include <stdlib.h>
28
29
30

#include <gtk/gtk.h>

31
32
#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>
33

34
#include "libgimp/stdplugins-intl.h"
Marc Lehmann's avatar
Marc Lehmann committed
35
36
37
38

#define PRV_WIDTH 40
#define PRV_HEIGHT 20

39
40
typedef struct
{
Marc Lehmann's avatar
Marc Lehmann committed
41
42
43
  guchar color[3];
} C2AValues;

44
45
typedef struct
{
Marc Lehmann's avatar
Marc Lehmann committed
46
47
48
  gint run;
} C2AInterface;

49
50
51
typedef struct
{
  GtkWidget *color_button;
Marc Lehmann's avatar
Marc Lehmann committed
52
53
54
55
56
} C2APreview;

/* Declare local functions.
 */
static void      query  (void);
57
58
static void      run    (gchar     *name,
			 gint       nparams,
Marc Lehmann's avatar
Marc Lehmann committed
59
			 GParam    *param,
60
			 gint      *nreturn_vals,
Marc Lehmann's avatar
Marc Lehmann committed
61
62
63
64
65
			 GParam   **return_vals);

static void      toalpha            (GDrawable  *drawable);

static void      toalpha_render_row (const guchar *src_row,
66
67
68
				     guchar       *dest_row,
				     gint          row_width,
				     const gint    bytes);
Marc Lehmann's avatar
Marc Lehmann committed
69
/* UI stuff */
70
71
72
static gint 	colortoalpha_dialog      (GDrawable *drawable);
static void 	colortoalpha_ok_callback (GtkWidget *widget,
					  gpointer   data);
73

Marc Lehmann's avatar
Marc Lehmann committed
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
static GRunModeType run_mode;

GPlugInInfo PLUG_IN_INFO =
{
  NULL,    /* init_proc */
  NULL,    /* quit_proc */
  query,   /* query_proc */
  run,     /* run_proc */
};

static C2AValues pvals = 
{ 
  {255, 255, 255} /* white default */
};

static C2AInterface pint = 
{
  FALSE
};

static C2APreview ppreview = 
{
  NULL
};

MAIN ()

static void
102
query (void)
Marc Lehmann's avatar
Marc Lehmann committed
103
104
105
{
  static GParamDef args[] =
  {
Sven Neumann's avatar
Sven Neumann committed
106
107
    { PARAM_INT32,    "run_mode", "Interactive, non-interactive" },
    { PARAM_IMAGE,    "image",    "Input image (unused)" },
Marc Lehmann's avatar
Marc Lehmann committed
108
    { PARAM_DRAWABLE, "drawable", "Input drawable" },
Sven Neumann's avatar
Sven Neumann committed
109
    { PARAM_COLOR,    "color",    "Color to remove"},
Marc Lehmann's avatar
Marc Lehmann committed
110
111
112
113
114
  };
  static GParamDef *return_vals = NULL;
  static int nargs = sizeof (args) / sizeof (args[0]);
  static int nreturn_vals = 0;

Sven Neumann's avatar
Sven Neumann committed
115
116
  INIT_I18N();

Marc Lehmann's avatar
Marc Lehmann committed
117
  gimp_install_procedure ("plug_in_colortoalpha",
Marc Lehmann's avatar
Marc Lehmann committed
118
119
			  "Convert the color in an image to alpha",
			  "This replaces as much of a given color as possible in each pixel with a corresponding amount of alpha, then readjusts the color accordingly.",
Marc Lehmann's avatar
Marc Lehmann committed
120
121
			  "Seth Burgess",
			  "Seth Burgess <sjburges@gimp.org>",
Marc Lehmann's avatar
Marc Lehmann committed
122
			  "7th Aug 1999",
Sven Neumann's avatar
Sven Neumann committed
123
			  N_("<Image>/Filters/Colors/Color to Alpha..."),
Marc Lehmann's avatar
Marc Lehmann committed
124
125
126
127
128
129
130
			  "RGBA",
			  PROC_PLUG_IN,
			  nargs, nreturn_vals,
			  args, return_vals);
}

static void
131
132
run (gchar   *name,
     gint     nparams,
Marc Lehmann's avatar
Marc Lehmann committed
133
     GParam  *param,
134
     gint    *nreturn_vals,
Marc Lehmann's avatar
Marc Lehmann committed
135
136
137
138
139
140
141
142
143
144
145
146
147
     GParam **return_vals)
{
  static GParam values[1];
  GDrawable *drawable;
  gint32 image_ID;

  GStatusType status = STATUS_SUCCESS;

  run_mode = param[0].data.d_int32;

  *nreturn_vals = 1;
  *return_vals = values;

Sven Neumann's avatar
Sven Neumann committed
148
149
  INIT_I18N_UI();

Marc Lehmann's avatar
Marc Lehmann committed
150
151
152
153
154
155
156
157
158
  values[0].type = PARAM_STATUS;
  values[0].data.d_status = status;

  /*  Get the specified drawable  */
  drawable = gimp_drawable_get (param[2].data.d_drawable);
  image_ID = param[1].data.d_image;

  switch (run_mode)
    {
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
    case RUN_INTERACTIVE:
      gimp_get_data ("plug_in_colortoalpha", &pvals);
      if (! colortoalpha_dialog (drawable ))
	{ 
	  gimp_drawable_detach (drawable);
	  return;
	}
      break;
 
    case RUN_NONINTERACTIVE:
      if (nparams != 3)
	status = STATUS_CALLING_ERROR;
      if (status == STATUS_SUCCESS)
	{
	  pvals.color[0] = param[3].data.d_color.red;
	  pvals.color[1] = param[3].data.d_color.green;
	  pvals.color[2] = param[3].data.d_color.blue;
	}
      break;
Marc Lehmann's avatar
Marc Lehmann committed
178

179
180
181
182
183
184
    case RUN_WITH_LAST_VALS:
      gimp_get_data ("plug_in_colortoalpha", &pvals);
      break;
    default:
      break;
    }  
Marc Lehmann's avatar
Marc Lehmann committed
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206

  if (status == STATUS_SUCCESS)
    {
      /*  Make sure that the drawable is indexed or RGB color  */
      if (gimp_drawable_color (drawable->id) && 
          gimp_drawable_has_alpha(drawable->id))
	{
          if (run_mode != RUN_NONINTERACTIVE)
	    gimp_progress_init ("Removing color...");
             
	  toalpha (drawable);
	  gimp_displays_flush ();
	}
    }
  if (run_mode == RUN_INTERACTIVE)
    gimp_set_data ("plug_in_colortoalpha", &pvals, sizeof (pvals));

  values[0].data.d_status = status;
  gimp_drawable_detach (drawable);
}

void
207
208
209
210
211
212
213
colortoalpha (float *a1,
	      float *a2,
	      float *a3,
	      float *a4,
	      float c1,
	      float c2,
	      float c3)
Marc Lehmann's avatar
Marc Lehmann committed
214
{
215
216
217
218
  float alpha1, alpha2, alpha3, alpha4;
  
  alpha4 = *a4;
	  
Marc Lehmann's avatar
Marc Lehmann committed
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
  if ( *a1 > c1 )
    alpha1 = (*a1 - c1)/(255.0-c1);
  else if ( *a1 < c1 )
    alpha1 = (c1 - *a1)/(c1);
  else alpha1 = 0.0;

  if ( *a2 > c2 )
    alpha2 = (*a2 - c2)/(255.0-c2);
  else if ( *a2 < c2 )
    alpha2 = (c2 - *a2)/(c2);
  else alpha2 = 0.0;

  if ( *a3 > c3 )
    alpha3 = (*a3 - c3)/(255.0-c3);
  else if ( *a3 < c3 )
    alpha3 = (c3 - *a3)/(c3);
  else alpha3 = 0.0;

  if ( alpha1 > alpha2 )
    if ( alpha1 > alpha3 )
      {
	*a4 = alpha1;
      }
    else
      {
	*a4 = alpha3;
      }
  else
    if ( alpha2 > alpha3 )
      {
	*a4 = alpha2;
      }
    else
      {
	*a4 = alpha3;
      }
255
  
Marc Lehmann's avatar
Marc Lehmann committed
256
  *a4 *= 255.0;
257
  
Marc Lehmann's avatar
Marc Lehmann committed
258
259
260
261
262
263
  if ( *a4 < 1.0 )
    return;
  *a1 = 255.0 * (*a1-c1)/ *a4 + c1;
  *a2 = 255.0 * (*a2-c2)/ *a4 + c2;
  *a3 = 255.0 * (*a3-c3)/ *a4 + c3;

264
  *a4 *= alpha4/255.0;
Marc Lehmann's avatar
Marc Lehmann committed
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
}
/*
<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 a1 = alpha + c1(1-alpha) and therefore alpha = (a1-c1)/(1-c1).
<AmJur2d> eek!  math!
> AmJur2d runs and hides behind a library carrel
<sjburges> clahey: btw, the ordering of that a2, a3 in the white->alpha didn't matter
<clahey> sjburges: You mean that it could be either a1, a2, a3 or a1, a3, a2?
<sjburges> yeah
<sjburges> because neither one uses the other
<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.
<clahey> Then a2 = b2 * alpha + c2 * ( 1- alpha).  Solving for b2 gives b2 = (a1-c2)/alpha + c2.
<sjburges> yeah
<jlb> xachbot, url holy wars
<jlb> xachbot, url wars
<clahey> That gives us are formula for if the background is darker than the foreground? Yep.
<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.
<clahey> And then again, b2 = (a2-c2)/alpha + c2 by the same formula.  (Actually, I think we can use that formula for all cases, though it may possibly introduce rounding error.
<clahey> sjburges: I like the idea of using floats to avoid rounding error.  Good call.
<clahey> It's cool to be able to replace all the black in an image with another color.  It'
*/

static void
toalpha_render_row (const guchar *src_data,
288
289
290
		    guchar       *dest_data,
		    gint          col,               /* row width in pixels */
		    const gint    bytes)
Marc Lehmann's avatar
Marc Lehmann committed
291
292
293
294
295
296
297
298
299
300
301
302
303
304
{
  while (col--)
    {
      float v1, v2, v3, v4;

      v1 = (float)src_data[col*bytes   ];
      v2 = (float)src_data[col*bytes +1];
      v3 = (float)src_data[col*bytes +2];
      v4 = (float)src_data[col*bytes +3];
      /* For brighter than the background the rule is to send the
	 farthest above the background as the first address.
	 However, since v1 < COLOR_RED, for example, all of these
	 are negative so we have to invert the operator to reduce
	 the amount of typing to fix the problem.  :) */
305
306
307
308
309
310
311
312
313
      colortoalpha (&v1, &v2, &v3, &v4, 
		    (float)pvals.color[0],
		    (float)pvals.color[1],
		    (float)pvals.color[2]);

      dest_data[col * bytes    ] = (int) v1;
      dest_data[col * bytes + 1] = (int) v2;
      dest_data[col * bytes + 2] = (int) v3;
      dest_data[col * bytes + 3] = (int) v4;
Marc Lehmann's avatar
Marc Lehmann committed
314
315
316
317
318
319
320
321
322
323
324
325
326
    }
}

static void
toalpha_render_region (const GPixelRgn srcPR,
		       const GPixelRgn destPR)
{
  gint row;
  guchar* src_ptr  = srcPR.data;
  guchar* dest_ptr = destPR.data;
  
  for (row = 0; row < srcPR.h ; row++)
    {
327
328
329
330
      if (srcPR.bpp!=4)
	{
	  gimp_message ("Not the proper bpp! \n");
	  exit(1);
Marc Lehmann's avatar
Marc Lehmann committed
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
	} 
      toalpha_render_row (src_ptr, dest_ptr,
			  srcPR.w,
			  srcPR.bpp) ;

      src_ptr  += srcPR.rowstride;
      dest_ptr += destPR.rowstride;
    }
}

static void
toalpha (GDrawable *drawable)
{
  GPixelRgn srcPR, destPR;
  gint x1, y1, x2, y2;
  gpointer pr;
  gint total_area, area_so_far;
  gint progress_skip;

  /* Get the input area. This is the bounding box of the selection in
   *  the image (or the entire image if there is no selection). Only
   *  operating on the input area is simply an optimization. It doesn't
   *  need to be done for correct operation. (It simply makes it go
   *  faster, since fewer pixels need to be operated on).
   */
  gimp_drawable_mask_bounds (drawable->id, &x1, &y1, &x2, &y2);

  total_area = (x2 - x1) * (y2 - y1);
  area_so_far = 0;
  progress_skip = 0;

  /* Initialize the pixel regions. */
  gimp_pixel_rgn_init (&srcPR, drawable, x1, y1, (x2 - x1), (y2 - y1),
		       FALSE, FALSE);
  gimp_pixel_rgn_init (&destPR, drawable, x1, y1, (x2 - x1), (y2 - y1),
		       TRUE, TRUE);
  
  for (pr = gimp_pixel_rgns_register (2, &srcPR, &destPR);
       pr != NULL;
       pr = gimp_pixel_rgns_process (pr))
    {
      toalpha_render_region (srcPR, destPR);

      if ((run_mode != RUN_NONINTERACTIVE))
	{
	  area_so_far += srcPR.w * srcPR.h;
	  if (((progress_skip++)%10) == 0)
	    gimp_progress_update ((double) area_so_far / (double) total_area);
	}
    }

  /*  update the processed region  */
  gimp_drawable_flush (drawable);
  gimp_drawable_merge_shadow (drawable->id, TRUE);
  gimp_drawable_update (drawable->id, x1, y1, (x2 - x1), (y2 - y1));
}

static gint 
389
colortoalpha_dialog (GDrawable *drawable)
Marc Lehmann's avatar
Marc Lehmann committed
390
391
{
  GtkWidget *dlg;
392
  GtkWidget *frame;
Marc Lehmann's avatar
Marc Lehmann committed
393
  GtkWidget *table;
394
  GtkWidget *button;
Marc Lehmann's avatar
Marc Lehmann committed
395
396
  GtkWidget *label;

397
398
399
  guchar  *color_cube;
  gchar  **argv;
  gint     argc;
Marc Lehmann's avatar
Marc Lehmann committed
400
  
401
402
  argc    = 1;
  argv    = g_new (gchar *, 1);
Marc Lehmann's avatar
Marc Lehmann committed
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
  argv[0] = g_strdup ("colortoalpha");

  gtk_init (&argc, &argv);
  gtk_rc_parse (gimp_gtkrc ());

  gdk_set_use_xshm (gimp_use_xshm ());
  gtk_preview_set_gamma (gimp_gamma ());
  gtk_preview_set_install_cmap (gimp_install_cmap ());
  color_cube = gimp_color_cube ();
  gtk_preview_set_color_cube (color_cube[0], color_cube[1],
                              color_cube[2], color_cube[3]);

  gtk_widget_set_default_visual (gtk_preview_get_visual ());
  gtk_widget_set_default_colormap (gtk_preview_get_cmap ());

418
419
420
421
422
  dlg = gimp_dialog_new (_("Color to Alpha"), "colortoalpha",
			 gimp_plugin_help_func, "filters/colortoalpha.html",
			 GTK_WIN_POS_MOUSE,
			 FALSE, TRUE, FALSE,

423
			 _("OK"), colortoalpha_ok_callback,
424
425
426
427
428
429
			 NULL, NULL, NULL, TRUE, FALSE,
			 _("Cancel"), gtk_widget_destroy,
			 NULL, 1, NULL, FALSE, TRUE,

			 NULL);

Marc Lehmann's avatar
Marc Lehmann committed
430
  gtk_signal_connect (GTK_OBJECT (dlg), "destroy",
431
                      GTK_SIGNAL_FUNC (gtk_main_quit),
Marc Lehmann's avatar
Marc Lehmann committed
432
433
                      NULL);

434
435
436
437
438
  frame = gtk_frame_new (_("Color"));
  gtk_container_set_border_width (GTK_CONTAINER (frame), 6);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), frame, FALSE, FALSE, 0);
  gtk_widget_show (frame);

439
  table = gtk_table_new (1, 3, FALSE);
440
441
442
  gtk_table_set_col_spacings (GTK_TABLE (table), 6);
  gtk_container_set_border_width (GTK_CONTAINER (table), 4);
  gtk_container_add (GTK_CONTAINER (frame), table);
443
444
  gtk_widget_show (table);
   
445
  label = gtk_label_new (_("From:"));
Sven Neumann's avatar
Sven Neumann committed
446
  gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.5);
447
  gtk_table_attach_defaults (GTK_TABLE(table), label, 0, 1, 0, 1);
448
  gtk_widget_show (label);
449

Sven Neumann's avatar
Sven Neumann committed
450
  button = gimp_color_button_new (_("Color to Alpha Color Picker"), 
451
452
				  PRV_WIDTH, PRV_HEIGHT,
				  pvals.color, 3);
453
  gtk_table_attach (GTK_TABLE (table), button, 1, 2, 0, 1, 
454
		    GTK_FILL, GTK_SHRINK, 0, 0) ; 
455
  gtk_widget_show (button);
456
  ppreview.color_button = button;
Marc Lehmann's avatar
Marc Lehmann committed
457

458
  label = gtk_label_new (_("to Alpha"));
Sven Neumann's avatar
Sven Neumann committed
459
  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
460
  gtk_table_attach_defaults (GTK_TABLE(table), label, 2, 3, 0, 1);
461
  gtk_widget_show (label);
Marc Lehmann's avatar
Marc Lehmann committed
462

463
  gtk_widget_show (dlg);
Marc Lehmann's avatar
Marc Lehmann committed
464

465
466
  gtk_main ();
  gdk_flush ();
Marc Lehmann's avatar
Marc Lehmann committed
467

468
  return pint.run;
Marc Lehmann's avatar
Marc Lehmann committed
469
470
471
}

static void
472
473
colortoalpha_ok_callback (GtkWidget *widget, 
			  gpointer   data)
Marc Lehmann's avatar
Marc Lehmann committed
474
475
{
  pint.run = TRUE;
476

Marc Lehmann's avatar
Marc Lehmann committed
477
478
  gtk_widget_destroy (GTK_WIDGET (data));
}