color-to-alpha.c 9 KB
Newer Older
1 2
/* This file is an image processing operation for GEGL
 *
3 4 5 6
 * 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 3 of the License, or
 * (at your option) any later version.
7
 *
8
 * This program is distributed in the hope that it will be useful,
9
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
12
 *
13
 * You should have received a copy of the GNU General Public License
14
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
15 16 17 18 19
 *
 * Color To Alpha plug-in v1.0 by Seth Burgess, sjburges@gimp.org 1999/05/14
 *  with algorithm by clahey
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 * Copyright (C) 2011 Robert Sasu <sasu.robert@gmail.com>
20
 * Copyright (C) 2012 Øyvind Kolås <pippin@gimp.org>
21
 * Copyright (C) 2017 Ell
22 23 24 25 26
 */

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

27
#ifdef GEGL_PROPERTIES
28

29
property_color (color, _("Color"), "white")
30
    description(_("The color to make transparent."))
31

32 33 34 35 36 37 38 39
property_double (transparency_threshold, _("Transparency threshold"), 0.0)
    description(_("The limit below which colors become transparent."))
    value_range (0.0, 1.0)

property_double (opacity_threshold, _("Opacity threshold"), 1.0)
    description(_("The limit above which colors remain opaque."))
    value_range (0.0, 1.0)

40 41
#else

42
#define GEGL_OP_POINT_FILTER
43
#define GEGL_OP_NAME     color_to_alpha
44
#define GEGL_OP_C_SOURCE color-to-alpha.c
45

46
#include "gegl-op.h"
47 48 49
#include <stdio.h>
#include <math.h>

50 51
#define EPSILON 0.00001

52 53
static void
prepare (GeglOperation *operation)
54
{
55
  const Babl     *space  = gegl_operation_get_source_space (operation, "input");
56
  gegl_operation_set_format (operation, "input",
57
                             babl_format_with_space ("R'G'B'A float", space));
58
  gegl_operation_set_format (operation, "output",
59
                             babl_format_with_space ("R'G'B'A float", space));
60 61 62 63 64 65
}

/*
 * An excerpt from a discussion on #gimp that sheds some light on the ideas
 * behind the algorithm that is being used here.
 *
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
 <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).
 <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
 <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.
*/
94 95

static void
96 97
color_to_alpha (const gfloat *color,
                const gfloat *src,
98 99 100
                gfloat       *dst,
                gfloat        transparency_threshold,
                gfloat        opacity_threshold)
101
{
102 103 104
  gint   i;
  gfloat dist  = 0.0f;
  gfloat alpha = 0.0f;
105

106
  for (i = 0; i < 4; i++)
107 108
    dst[i] = src[i];

109
  for (i = 0; i < 3; i++)
110
    {
111 112
      gfloat d;
      gfloat a;
113

114 115 116 117 118 119 120 121
      d = fabsf (dst[i] - color[i]);

      if (d < transparency_threshold + EPSILON)
        a = 0.0f;
      else if (d > opacity_threshold - EPSILON)
        a = 1.0f;
      else if (dst[i] < color[i])
        a = (d - transparency_threshold) / (MIN (opacity_threshold,        color[i]) - transparency_threshold);
122
      else
123 124 125 126 127 128 129
        a = (d - transparency_threshold) / (MIN (opacity_threshold, 1.0f - color[i]) - transparency_threshold);

      if (a > alpha)
        {
          alpha = a;
          dist  = d;
        }
130
    }
131 132

  if (alpha > EPSILON)
133
    {
134 135 136 137 138 139
      gfloat ratio     = transparency_threshold / dist;
      gfloat alpha_inv = 1.0f / alpha;

      for (i = 0; i < 3; i++)
        {
          gfloat c;
140

141
          c = color[i] + (dst[i] - color[i]) * ratio;
142

143 144 145
          dst[i] = c + (dst[i] - c) * alpha_inv;
        }
    }
146

147
  dst[3] *= alpha;
148 149
}

150 151 152 153 154 155 156

/* FIXME:  the transparency-threshold and opacity-threshold properties are not
 * handled by the opencl version atm.  re-enable the opencl version once
 * they're implemented.
 */
#if 0

157 158 159 160 161 162 163 164 165 166 167 168 169
#include "opencl/gegl-cl.h"
#include "opencl/color-to-alpha.cl.h"

static GeglClRunData * cl_data = NULL;

static gboolean
cl_process (GeglOperation       *operation,
            cl_mem              in,
            cl_mem              out,
            size_t              global_worksize,
            const GeglRectangle *roi,
            gint                level)
{
170
  GeglProperties *o = GEGL_PROPERTIES (operation);
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
  gfloat      color[4];
  gegl_color_get_pixel (o->color, babl_format ("R'G'B'A float"), color);

  if (!cl_data)
    {
      const char *kernel_name[] = {"cl_color_to_alpha",NULL};
      cl_data = gegl_cl_compile_and_build (color_to_alpha_cl_source, kernel_name);
    }
  if (!cl_data)
    return TRUE;
  else
  {
    cl_int cl_err = 0;
    cl_float4 f_color;
    f_color.s[0] = color[0];
    f_color.s[1] = color[1];
    f_color.s[2] = color[2];
    f_color.s[3] = color[3];

    cl_err = gegl_clSetKernelArg(cl_data->kernel[0], 0,  sizeof(cl_mem),   (void*)&in);
    CL_CHECK;
    cl_err = gegl_clSetKernelArg(cl_data->kernel[0], 1,  sizeof(cl_mem),   (void*)&out);
    CL_CHECK;
    cl_err = gegl_clSetKernelArg(cl_data->kernel[0], 2,  sizeof(cl_float4),(void*)&f_color);
    CL_CHECK;

    cl_err = gegl_clEnqueueNDRangeKernel(gegl_cl_get_command_queue (),
                                         cl_data->kernel[0], 1,
                                         NULL, &global_worksize, NULL,
                                         0, NULL, NULL);
    CL_CHECK;
  }

  return  FALSE;

 error:
  return TRUE;
}
209

210
#endif
211 212 213

static gboolean
process (GeglOperation       *operation,
214 215 216 217
         void                *in_buf,
         void                *out_buf,
         glong                n_pixels,
         const GeglRectangle *roi,
218
         gint                 level)
219
{
220
  GeglProperties *o                      = GEGL_PROPERTIES (operation);
221
  const Babl     *format                 = gegl_operation_get_format (operation, "output");
222 223 224 225
  gfloat          color[4];
  gfloat          transparency_threshold = o->transparency_threshold;
  gfloat          opacity_threshold      = o->opacity_threshold;
  gint            x;
226

227 228
  gfloat *in_buff = in_buf;
  gfloat *out_buff = out_buf;
229

230
  gegl_color_get_pixel (o->color, format, color);
231

232 233
  for (x = 0; x < n_pixels; x++)
    {
234 235
      color_to_alpha (color, in_buff, out_buff,
                      transparency_threshold, opacity_threshold);
236 237 238
      in_buff  += 4;
      out_buff += 4;
    }
239

240
  return TRUE;
241 242 243
}

static void
244
gegl_op_class_init (GeglOpClass *klass)
245
{
246
  GeglOperationClass            *operation_class;
247
  GeglOperationPointFilterClass *filter_class;
248 249
  gchar                         *composition =
    "<?xml version='1.0' encoding='UTF-8'?>"
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
    "<gegl>"
    "<node operation='svg:dst-over'>"
    "  <node operation='gegl:crop'>"
    "    <params>"
    "      <param name='width'>200.0</param>"
    "      <param name='height'>200.0</param>"
    "    </params>"
    "  </node>"
    "  <node operation='gegl:checkerboard'>"
    "    <params><param name='color1'>rgb(0.5, 0.5, 0.5)</param></params>"
    "  </node>"
    "</node>"
    "<node operation='gegl:color-to-alpha'>"
    "</node>"
    "<node operation='gegl:load'>"
    "  <params>"
    "    <param name='path'>standard-input.png</param>"
    "  </params>"
    "</node>"
    "</gegl>";
270 271

  operation_class = GEGL_OPERATION_CLASS (klass);
272
  filter_class    = GEGL_OPERATION_POINT_FILTER_CLASS (klass);
273 274

  filter_class->process    = process;
275
#if 0 /* see opencl comment above */
276
  filter_class->cl_process = cl_process;
277
#endif
278

279
  operation_class->prepare = prepare;
280
#if 0 /* see opencl comment above */
281
  operation_class->opencl_support = TRUE;
282
#endif
283

284
  gegl_operation_class_set_keys (operation_class,
285
    "name",        "gegl:color-to-alpha",
286
    "title",       _("Color to Alpha"),
287
    "categories",  "color",
288 289
    "needs-alpha",  "true", /* hint for GIMP that layers this op is performed on
                               need to have alpha added */
290
    "license",     "GPL3+",
291
    "reference-hash", "f110613097308e0fe96ac29f54ca4c2e",
292
    "description", _("Convert a specified color to transparency, works best with white."),
293
    "reference-composition", composition,
294
    NULL);
295 296 297
}

#endif