cartoon.c 9.72 KB
Newer Older
Angh's avatar
Angh committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/* 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
 * License along with GEGL; if not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright 1997 Spencer Kimball
 * Copyright 2012 Maxime Nicco <maxime.nicco@gmail.com>
 */

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

23
#ifdef GEGL_PROPERTIES
Angh's avatar
Angh committed
24

25 26
property_double (mask_radius, _("Mask radius"), 7.0)
    value_range (0.0, 50.0)
Angh's avatar
Angh committed
27

28 29
property_double (pct_black, _("Percent black"), 0.2)
    value_range (0.0, 1.0)
Angh's avatar
Angh committed
30 31 32

#else

33
#define GEGL_OP_AREA_FILTER
34
#define GEGL_OP_NAME     cartoon
35
#define GEGL_OP_C_SOURCE cartoon.c
Angh's avatar
Angh committed
36

37
#include "gegl-op.h"
Angh's avatar
Angh committed
38 39 40 41 42 43 44 45 46 47 48 49 50
#include <stdio.h>
#include <math.h>
#include <stdlib.h>

#define THRESHOLD 1.0

typedef struct {
  gdouble      prev_mask_radius;
  gdouble      prev_pct_black;
  gdouble      prev_ramp;
} Ramps;

static void
51 52 53 54
grey_blur_buffer (GeglBuffer  *input,
                  gdouble      mask_radius,
                  GeglBuffer **dest1,
                  GeglBuffer **dest2)
Angh's avatar
Angh committed
55 56 57 58
{
  GeglNode *gegl, *image, *write1, *write2, *grey, *blur1, *blur2;
  gdouble radius, std_dev1, std_dev2;

59 60 61 62 63 64 65 66
  gegl = gegl_node_new ();
  image = gegl_node_new_child (gegl,
                "operation", "gegl:buffer-source",
                "buffer", input,
                NULL);
  grey = gegl_node_new_child (gegl,
                "operation", "gegl:grey",
                NULL);
Angh's avatar
Angh committed
67 68 69 70

  radius   = 1.0;
  radius   = fabs (radius) + 1.0;
  std_dev1 = sqrt (-(radius * radius) / (2 * log (1.0 / 255.0)));
71

Angh's avatar
Angh committed
72 73 74
  radius   = fabs (mask_radius) + 1.0;
  std_dev2 = sqrt (-(radius * radius) / (2 * log (1.0 / 255.0)));

75 76 77 78 79 80 81 82 83 84
  blur1 =  gegl_node_new_child (gegl,
                "operation", "gegl:gaussian-blur",
                "std_dev_x", std_dev1,
                "std_dev_y", std_dev1,
                NULL);
  blur2 =  gegl_node_new_child (gegl,
                "operation", "gegl:gaussian-blur",
                "std_dev_x", std_dev2,
                "std_dev_y", std_dev2,
                NULL);
Angh's avatar
Angh committed
85 86

  write1 = gegl_node_new_child(gegl,
87 88
                "operation", "gegl:buffer-sink",
                "buffer", dest1, NULL);
89

Angh's avatar
Angh committed
90
  write2 = gegl_node_new_child(gegl,
91 92
                "operation", "gegl:buffer-sink",
                "buffer", dest2, NULL);
93

Angh's avatar
Angh committed
94 95
  gegl_node_link_many (image, grey, blur1, write1, NULL);
  gegl_node_process (write1);
96

Angh's avatar
Angh committed
97 98 99 100 101 102 103
  gegl_node_link_many (grey, blur2, write2, NULL);
  gegl_node_process (write2);

  g_object_unref (gegl);
}

static gdouble
104 105 106 107
compute_ramp (GeglSampler         *sampler1,
              GeglSampler         *sampler2,
              const GeglRectangle *roi,
              gdouble              pct_black)
Angh's avatar
Angh committed
108 109 110 111 112 113 114 115 116
{
  gint    hist[100];
  gdouble diff;
  gint    count;
  gfloat pixel1, pixel2;
  gint x;
  gint y;
  gint i;
  gint sum;
117

Angh's avatar
Angh committed
118 119 120
  memset (hist, 0, sizeof (int) * 100);
  count = 0;

121 122
  for (y = roi->y; y < roi->y + roi->height; ++y)
    for (x = roi->x; x < roi->x + roi->width; ++x)
Angh's avatar
Angh committed
123
      {
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
        gegl_sampler_get (sampler1,
                          x,
                          y,
                          NULL,
                          &pixel1,
                          GEGL_ABYSS_NONE);

        gegl_sampler_get (sampler2,
                          x,
                          y,
                          NULL,
                          &pixel2,
                          GEGL_ABYSS_NONE);

        if (pixel2 != 0)
          {
            diff = (gdouble) pixel1 / (gdouble) pixel2;

            if (diff < 1.0 && diff >= 0.0)
              {
                hist[(int) (diff * 100)] += 1;
                count += 1;
              }
          }
Angh's avatar
Angh committed
148
      }
149

Angh's avatar
Angh committed
150 151 152 153 154
  if (pct_black == 0.0 || count == 0)
    return 1.0;

  sum = 0;
  for (i = 0; i < 100; i++)
155 156 157 158 159
    {
      sum += hist[i];
      if (((gdouble) sum / (gdouble) count) > pct_black)
        return (1.0 - (gdouble) i / 100.0);
    }
Angh's avatar
Angh committed
160 161 162 163 164

  return 0.0;

}

165 166
static void
prepare (GeglOperation *operation)
Angh's avatar
Angh committed
167 168 169 170 171 172 173
{
  gegl_operation_set_format (operation, "input",
                             babl_format ("Y'CbCrA float"));
  gegl_operation_set_format (operation, "output",
                             babl_format ("Y'CbCrA float"));
}

174 175 176 177 178 179 180 181 182 183 184 185 186
static GeglRectangle
get_bounding_box (GeglOperation *operation)
{
  GeglRectangle *region;

  region = gegl_operation_source_get_bounding_box (operation, "input");

  if (region != NULL)
    return *region;
  else
    return *GEGL_RECTANGLE (0, 0, 0, 0);
}

187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
static GeglRectangle
get_required_for_output (GeglOperation       *operation,
                         const gchar         *input_pad,
                         const GeglRectangle *output_roi)
{
  GeglRectangle *in_rect = gegl_operation_source_get_bounding_box (operation, input_pad);

  if (in_rect)
    return *in_rect;
  else
    return (GeglRectangle){0, 0, 0, 0};
}

static GeglRectangle
get_cached_region (GeglOperation       *operation,
                   const GeglRectangle *output_roi)
{
  GeglRectangle *in_rect = gegl_operation_source_get_bounding_box (operation, "input");

  if (in_rect)
    return *in_rect;
  else
    return (GeglRectangle){0, 0, 0, 0};
}

Angh's avatar
Angh committed
212 213 214 215 216 217 218
static gboolean
process (GeglOperation       *operation,
         GeglBuffer          *input,
         GeglBuffer          *output,
         const GeglRectangle *result,
         gint                 level)
{
219
  GeglProperties         *o = GEGL_PROPERTIES (operation);
220 221 222 223 224 225 226 227
  GeglBufferIterator *iter;
  GeglBuffer         *dest1;
  GeglBuffer         *dest2;
  GeglSampler        *sampler1;
  GeglSampler        *sampler2;
  gdouble             ramp;
  gint                x;
  gint                y;
228 229
  gfloat              tot_pixels = result->width * result->height;
  gfloat              pixels = 0;
Angh's avatar
Angh committed
230

231
  grey_blur_buffer (input, o->mask_radius, &dest1, &dest2);
232

233
  sampler1 = gegl_buffer_sampler_new_at_level (dest1,
234 235 236
                                               babl_format ("Y' float"),
                                               GEGL_SAMPLER_LINEAR,
                                               level);
237

238
  sampler2 = gegl_buffer_sampler_new_at_level (dest2,
239 240 241
                                               babl_format ("Y' float"),
                                               GEGL_SAMPLER_LINEAR,
                                               level);
242

243
  ramp = compute_ramp (sampler1, sampler2, result, o->pct_black);
244

245 246
  iter = gegl_buffer_iterator_new (output, result, 0,
                                   babl_format ("Y'CbCrA float"),
247
                                   GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE);
248 249
  gegl_buffer_iterator_add (iter, input, result, 0,
                            babl_format ("Y'CbCrA float"),
250
                            GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
Angh's avatar
Angh committed
251 252


253 254
  gegl_operation_progress (operation, 0.0, "");

255 256 257 258 259 260
  while (gegl_buffer_iterator_next (iter))
    {
      gfloat *out_pixel = iter->data[0];
      gfloat *in_pixel  = iter->data[1];

      for (y = iter->roi[0].y; y < iter->roi[0].y + iter->roi[0].height; ++y)
261
      {
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
        for (x = iter->roi[0].x; x < iter->roi[0].x + iter->roi[0].width; ++x)
          {
            gfloat  pixel1;
            gfloat  pixel2;
            gdouble mult = 0.0;
            gdouble diff;

            gegl_sampler_get (sampler1, x, y,
                              NULL, &pixel1,
                              GEGL_ABYSS_NONE);

            gegl_sampler_get (sampler2, x, y,
                              NULL, &pixel2,
                              GEGL_ABYSS_NONE);

            if (pixel2 != 0)
              {
                diff = (gdouble) pixel1 / (gdouble) pixel2;
                if (diff < THRESHOLD)
                  {
                    if (GEGL_FLOAT_EQUAL (ramp, 0.0))
                      mult = 0.0;
                    else
                      mult = (ramp - MIN (ramp, (THRESHOLD - diff))) / ramp;
                  }
                else
                  mult = 1.0;
              }

            out_pixel[0] = CLAMP (pixel1 * mult, 0.0, 1.0);
            out_pixel[1] = in_pixel[1];
            out_pixel[2] = in_pixel[2];
            out_pixel[3] = in_pixel[3];

            out_pixel += 4;
            in_pixel  += 4;
298

299
          }
300
        pixels += iter->roi[0].width;
301
        gegl_operation_progress (operation, pixels / tot_pixels, "");
302
      }
Angh's avatar
Angh committed
303 304
    }

305 306
  gegl_operation_progress (operation, 1.0, "");

Angh's avatar
Angh committed
307 308
  g_object_unref (sampler1);
  g_object_unref (sampler2);
309

Angh's avatar
Angh committed
310 311
  g_object_unref (dest1);
  g_object_unref (dest2);
312

313
  return TRUE;
Angh's avatar
Angh committed
314 315 316
}

static void
317
gegl_op_class_init (GeglOpClass *klass)
Angh's avatar
Angh committed
318 319 320 321 322 323 324
{
  GeglOperationClass       *operation_class;
  GeglOperationFilterClass *filter_class;

  operation_class = GEGL_OPERATION_CLASS (klass);
  filter_class    = GEGL_OPERATION_FILTER_CLASS (klass);

325 326 327
  operation_class->prepare                 = prepare;
  operation_class->get_bounding_box        = get_bounding_box;
  operation_class->get_cached_region       = get_cached_region;
328
  operation_class->threaded                = FALSE;
329 330 331
  operation_class->get_required_for_output = get_required_for_output;

  filter_class->process = process;
Angh's avatar
Angh committed
332 333

  gegl_operation_class_set_keys (operation_class,
334 335
    "categories",  "artistic",
    "name",        "gegl:cartoon",
336 337 338
    "title",       _("Cartoon"),
    "license",     "GPL3+",
    "description", _("Simulates a cartoon, its result is similar to a black felt pen drawing subsequently shaded with color. This is achieved by enhancing edges and darkening areas that are already distinctly darker than their neighborhood"),
339
    NULL);
Angh's avatar
Angh committed
340 341 342
}

#endif