plasma.c 11.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/* 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/>.
 *
16 17 18 19 20
 * Copyright (C) 1996 Stephen Norris  (srn@flibble.cs.su.oz.au)
 * Copyright (C) 1996 Eiichi Takamori (taka@ma1.seikyou.ne.jp)
 * Copyright (C) 2000 Tim Copperfield (timecop@japan.co.jp)
 * Copyright (C) 2011 Robert Sasu     (sasu.robert@gmail.com)
 * Copyright (C) 2013 Téo Mazars      (teo.mazars@ensimag.fr)
21 22
 */

23 24 25 26 27 28 29
/*
 * This plug-in produces plasma fractal images. The algorithm is losely
 * based on a description of the fractint algorithm, but completely
 * re-implemented because the fractint code was too ugly to read :). It
 * was written by Stephen Norris for GIMP, and was ported to GEGL in
 * 2011 by Robert Sasu.
 */
30 31 32 33

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

34 35
#ifdef GEGL_PROPERTIES

36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
property_double (turbulence, _("Turbulence"), 1.0)
    description (_("High values give more variation in details"))
    value_range (0.0, 7.0)

property_int   (x, _("X"), 0)
    description(_("X start of the generated buffer"))
    ui_range   (-4096, 4096)
    ui_meta    ("unit", "pixel-coordinate")
    ui_meta    ("axis", "x")
    ui_meta    ("role", "output-extent")

property_int   (y, _("Y"), 0)
    description(_("Y start of the generated buffer"))
    ui_range   (-4096, 4096)
    ui_meta    ("unit", "pixel-coordinate")
    ui_meta    ("axis", "y")
    ui_meta    ("role", "output-extent")

property_int    (width, _("Width"), 1024)
    description (_("Width of the generated buffer"))
    value_range (0, G_MAXINT)
    ui_range    (0, 4096)
    ui_meta     ("unit", "pixel-distance")
    ui_meta     ("axis", "x")
    ui_meta     ("role", "output-extent")

property_int (height, _("Height"), 768)
    description(_("Height of the generated buffer"))
    value_range (0, G_MAXINT)
    ui_range    (0, 4096)
    ui_meta     ("unit", "pixel-distance")
    ui_meta     ("axis", "y")
    ui_meta     ("role", "output-extent")

property_seed (seed, _("Random seed"), rand)
71

72 73
#else

74 75
#define GEGL_OP_SOURCE
#define GEGL_OP_C_FILE "plasma.c"
76

77
#include "gegl-op.h"
78

79
#define TILE_SIZE 512
80

81 82
typedef struct
{
83 84 85 86 87 88 89 90
  GeglBuffer     *output;
  GRand          *gr;
  GeglProperties *o;
  float          *buffer;
  gboolean        using_buffer;
  gint            buffer_x;
  gint            buffer_y;
  gint            buffer_width;
91 92
} PlasmaContext;

93 94 95 96 97
static void
average_pixel (gfloat *dst_buf,
               gfloat *src_buf1,
               gfloat *src_buf2)
{
98
  gint i;
99

100 101
  for (i = 0; i < 3; i++)
    *dst_buf++ = (*src_buf1++ + *src_buf2++) / 2.0;
102 103 104 105 106 107 108
}

static void
random_rgba (GRand  *gr,
             gfloat *dest)
{
  gint i;
109

110 111
  for (i = 0; i < 3; i++)
    dest[i] = (gfloat) g_rand_double_range (gr, 0.0, 1.0);
112 113 114 115 116 117 118 119 120 121
}

static void
add_random (GRand  *gr,
            gfloat *dest,
            gfloat  amount)
{
  gint    i;
  gfloat  tmp;

122
  amount /= 2.0;
123 124

  if (amount > 0)
125
    for (i = 0; i < 3; i++)
126
      {
127 128
        tmp = dest[i] + (gfloat) g_rand_double_range (gr, -amount, amount);
        dest[i] = CLAMP (tmp, 0.0, 1.0);
129
      }
130 131 132
}

static void
133 134 135 136
put_pixel (PlasmaContext *context,
           gfloat        *pixel,
           gint           x,
           gint           y)
137
{
138 139 140 141 142 143 144 145 146
  if (G_UNLIKELY (!context->using_buffer))
    {
      GeglRectangle rect;

      rect.x = x;
      rect.y = y;
      rect.width = 1;
      rect.height = 1;

147 148
      gegl_buffer_set (context->output, &rect, 0, babl_format ("R'G'B' float"),
                       pixel, GEGL_AUTO_ROWSTRIDE);
149 150 151 152 153
      return;
    }
  else
    {
      float *ptr;
154
      gint index;
155

156 157 158 159
      index = ((y - context->buffer_y) * context->buffer_width +
               x - context->buffer_x);

      ptr = context->buffer + index * 3;
160

161 162 163 164
      *ptr++ = *pixel++;
      *ptr++ = *pixel++;
      *ptr++ = *pixel++;
    }
165 166
}

167
static gboolean
168 169 170 171 172
do_plasma (PlasmaContext *context,
           gint           x1,
           gint           y1,
           gint           x2,
           gint           y2,
173
           gint           plasma_depth,
174 175
           gint           recursion_depth,
           gint           level)
176
{
177 178
  gfloat tl[3], ml[3], bl[3], mt[3], mm[3], mb[3], tr[3], mr[3], br[3];
  gfloat tmp[3];
179 180 181
  gint    xm, ym;
  gfloat  ran;

182 183 184 185 186 187 188 189 190 191 192 193
  if (G_UNLIKELY ((!context->using_buffer) &&
                  ((x2 - x1 + 1) <= TILE_SIZE) &&
                  ((y2 - y1 + 1) <= TILE_SIZE)))
    {
      gboolean ret;
      GeglRectangle rect;

      rect.x = x1;
      rect.y = y1;
      rect.width = x2 - x1 + 1;
      rect.height = y2 - y1 + 1;

194
      gegl_buffer_get (context->output, &rect, 1.0, babl_format ("R'G'B' float"),
195
                       context->buffer, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
196 197 198 199 200 201

      context->using_buffer = TRUE;
      context->buffer_x = x1;
      context->buffer_y = y1;
      context->buffer_width = x2 - x1 + 1;

202
      ret = do_plasma (context, x1, y1, x2, y2, plasma_depth, recursion_depth, level);
203 204 205

      context->using_buffer = FALSE;

206
      gegl_buffer_set (context->output, &rect, 0, babl_format ("R'G'B' float"),
207 208 209 210 211
                       context->buffer, GEGL_AUTO_ROWSTRIDE);

      return ret;
    }

212 213 214
  xm = (x1 + x2) / 2;
  ym = (y1 + y2) / 2;

215
  if (plasma_depth == -1)
216
    {
217
      random_rgba (context->gr, tl);
218
      put_pixel (context, tl, x1, y1);
219

220
      random_rgba (context->gr, tr);
221
      put_pixel (context, tr, x2, y1);
222

223
      random_rgba (context->gr, bl);
224
      put_pixel (context, bl, x1, y2);
225

226
      random_rgba (context->gr, br);
227
      put_pixel (context, br, x2, y2);
228

229
      random_rgba (context->gr, mm);
230
      put_pixel (context, mm, xm, ym);
231

232
      random_rgba (context->gr, ml);
233
      put_pixel (context, ml, x1, ym);
234

235
      random_rgba (context->gr, mr);
236
      put_pixel (context, mr, x2, ym);
237

238
      random_rgba (context->gr, mt);
239
      put_pixel (context, mt, xm, y1);
240

241
      random_rgba (context->gr, mb);
242
      put_pixel (context, mb, xm, y2);
243

244
      return FALSE;
245 246
    }

247
  if (!plasma_depth)
248
    {
249 250
      if (x1 == x2 && y1 == y2)
        return FALSE;
251

252 253
      gegl_buffer_sample_at_level (context->output, x1, y1, NULL, tl,
                          babl_format ("R'G'B' float"), level,
254
                          GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
255 256
      gegl_buffer_sample_at_level (context->output, x1, y2, NULL, bl,
                          babl_format ("R'G'B' float"), level,
257
                          GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
258 259
      gegl_buffer_sample_at_level (context->output, x2, y1, NULL, tr,
                          babl_format ("R'G'B' float"), level,
260
                          GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
261 262
      gegl_buffer_sample_at_level (context->output, x2, y2, NULL, br,
                          babl_format ("R'G'B' float"), level,
263
                          GEGL_SAMPLER_NEAREST, GEGL_ABYSS_NONE);
264

265
      ran = context->o->turbulence / (2.0 * recursion_depth);
266 267

      if (xm != x1 || xm != x2)
268 269 270 271 272 273 274 275 276 277 278 279 280 281
        {
          /* Left. */
          average_pixel (ml, tl, bl);
          add_random (context->gr, ml, ran);
          put_pixel (context, ml, x1, ym);

          /* Right. */
          if (x1 != x2)
            {
              average_pixel (mr, tr, br);
              add_random (context->gr, mr, ran);
              put_pixel (context, mr, x2, ym);
            }
        }
282 283 284


      if (ym != y1 || ym != x2)
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
        {
          /* Bottom. */
          if (x1 != xm || ym != y2)
            {
              average_pixel (mb, bl, br);
              add_random (context->gr, mb, ran);
              put_pixel (context, mb, xm, y2);
            }

          if (y1 != y2)
            {
              /* Top. */
              average_pixel (mt, tl, tr);
              add_random (context->gr, mt, ran);
              put_pixel (context, mt, xm, y1);
            }
        }
302 303

      if (y1 != y2 || x1 != x2)
304
        {
305
          /* Middle pixel. */
306 307 308
          average_pixel (mm, tl, br);
          average_pixel (tmp, bl, tr);
          average_pixel (mm, mm, tmp);
309

310 311 312
          add_random (context->gr, mm, ran);
          put_pixel (context, mm, xm, ym);
        }
313 314

      return x2 - x1 < 3 && y2 - y1 < 3;
315 316 317 318
    }

  if (x1 < x2 || y1 < y2)
    {
319
      /* Top left. */
320
      do_plasma (context, x1, y1, xm, ym, plasma_depth - 1, recursion_depth + 1, level);
321
      /* Bottom left. */
322
      do_plasma (context, x1, ym, xm, y2, plasma_depth - 1, recursion_depth + 1, level);
323
      /* Top right. */
324
      do_plasma (context, xm, y1, x2, ym, plasma_depth - 1, recursion_depth + 1, level);
325
      /* Bottom right. */
326
      return do_plasma (context, xm, ym, x2, y2,
327
                        plasma_depth - 1, recursion_depth + 1, level);
328 329 330 331 332
    }

  return TRUE;
}

333 334
static void
prepare (GeglOperation *operation)
335
{
336
  gegl_operation_set_format (operation, "output", babl_format ("R'G'B' float"));
337 338 339 340 341
}

static gboolean
process (GeglOperation       *operation,
         GeglBuffer          *output,
342 343
         const GeglRectangle *result,
         gint                 level)
344
{
345
  PlasmaContext *context;
346 347
  gint           depth;
  gint           x, y;
348

349
  context = g_new (PlasmaContext, 1);
350
  context->o = GEGL_PROPERTIES (operation);
351
  context->output = output;
352
  context->buffer = g_malloc (TILE_SIZE * TILE_SIZE * 3 * sizeof (gfloat));
353
  context->using_buffer = FALSE;
354

355 356 357 358
  /*
   * The first time only puts seed pixels (corners, center of edges,
   * center of image)
   */
359 360
  x = result->x + result->width;
  y = result->y + result->height;
361

362
  context->gr = g_rand_new_with_seed (context->o->seed);
363

364
  do_plasma (context, result->x, result->y, x-1, y-1, -1, 0, level);
365

366 367 368
  /*
   * Now we recurse through the images, going deeper each time
   */
369
  depth = 1;
370
  while (!do_plasma (context, result->x, result->y, x-1, y-1, depth, 0, level))
371
    depth++;
372

373
  gegl_buffer_sample_cleanup (context->output);
374
  g_rand_free (context->gr);
375
  g_free (context->buffer);
376 377
  g_free (context);

378 379 380
  return TRUE;
}

381 382 383
static GeglRectangle
get_bounding_box (GeglOperation *operation)
{
384
  GeglProperties *o = GEGL_PROPERTIES (operation);
385

386
  return *GEGL_RECTANGLE (o->x, o->y, o->width, o->height);
387 388
}

389 390 391 392 393
static GeglRectangle
get_required_for_output (GeglOperation       *operation,
                         const gchar         *input_pad,
                         const GeglRectangle *roi)
{
394
  return get_bounding_box (operation);
395 396 397 398 399 400
}

static GeglRectangle
get_cached_region (GeglOperation       *operation,
                   const GeglRectangle *roi)
{
401
  return get_bounding_box (operation);
402 403 404
}

static void
405
gegl_op_class_init (GeglOpClass *klass)
406 407
{
  GeglOperationClass       *operation_class;
408
  GeglOperationSourceClass *source_class;
409 410

  operation_class = GEGL_OPERATION_CLASS (klass);
411
  source_class    = GEGL_OPERATION_SOURCE_CLASS (klass);
412

413
  source_class->process                    = process;
414 415
  operation_class->prepare                 = prepare;
  operation_class->get_required_for_output = get_required_for_output;
416
  operation_class->get_bounding_box        = get_bounding_box;
417 418
  operation_class->get_cached_region       = get_cached_region;

419
  gegl_operation_class_set_keys (operation_class,
420
    "name",               "gegl:plasma",
421
    "title",              _("Plasma"),
422 423
    "categories",         "render",
    "position-dependent", "true",
424
    "license",            "GPL3+",
425
    "description", _("Creates an image filled with a plasma effect."),
426
    NULL);
427 428 429
}

#endif