plasma.c 10.3 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
 * Copyright (C) 1996 Stephen Norris
 * Copyright (C) 2011 Robert Sasu (sasu.robert@gmail.com)
18 19
 */

20 21 22 23 24 25 26
/*
 * 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.
 */
27 28 29 30 31 32

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

#ifdef GEGL_CHANT_PROPERTIES

33
gegl_chant_int (seed, _("Seed"), -1, G_MAXINT, -1,
34
                _("Random seed. Passing -1 implies that the seed is randomly chosen."))
35
gegl_chant_double (turbulence, _("Turbulence"), 0.1, 7.0, 1.0,
36
                   _("The value of the turbulence"))
37 38 39 40 41 42 43 44

#else

#define GEGL_CHANT_TYPE_FILTER
#define GEGL_CHANT_C_FILE        "plasma.c"

#include "gegl-chant.h"
#include <math.h>
45 46

#define TILE_SIZE 128
47

48 49 50 51 52
typedef struct
{
  GeglBuffer *output;
  GRand      *gr;
  GeglChantO *o;
53 54 55 56 57
  float      *buffer;
  gboolean    using_buffer;
  gint        buffer_x;
  gint        buffer_y;
  gint        buffer_width;
58 59
} PlasmaContext;

60 61 62 63 64 65 66 67 68 69 70
static void prepare (GeglOperation *operation)
{
  gegl_operation_set_format (operation, "input", babl_format ("RGBA float"));
  gegl_operation_set_format (operation, "output", babl_format ("RGBA float"));
}

static void
average_pixel (gfloat *dst_buf,
               gfloat *src_buf1,
               gfloat *src_buf2)
{
71
  gint i;
72

73
  for (i = 0; i < 4; i++)
74
    *dst_buf++ = (*src_buf1++ + *src_buf2++) / 2;
75 76 77 78 79 80 81
}

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

83
  for (i = 0; i < 4; i++)
84
    dest[i] = (gfloat) g_rand_double_range (gr, 0, 1);
85 86 87 88 89 90 91 92 93 94 95 96 97
}

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

  amount /= 2;

  if (amount > 0)
98
    for (i = 0; i < 4; i++)
99
      {
100 101
        tmp = dest[i] + (gfloat) g_rand_double_range(gr, -amount, amount);
        dest[i] = CLAMP (tmp, 0, 1);
102
      }
103 104 105
}

static void
106 107 108 109
put_pixel (PlasmaContext *context,
           gfloat        *pixel,
           gint           x,
           gint           y)
110
{
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
  if (G_UNLIKELY (!context->using_buffer))
    {
      GeglRectangle rect;

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

      gegl_buffer_set (context->output, &rect, babl_format ("RGBA float"), pixel,
                       GEGL_AUTO_ROWSTRIDE);
      return;
    }
  else
    {
      float *ptr;

      ptr = context->buffer + ((y - context->buffer_y) * 4 * context->buffer_width) + ((x - context->buffer_x) * 4);
129

130 131 132 133 134
      *ptr++ = *pixel++;
      *ptr++ = *pixel++;
      *ptr++ = *pixel++;
      *ptr++ = *pixel++;
    }
135 136
}

137
static gboolean
138 139 140 141 142 143 144
do_plasma_big (PlasmaContext *context,
               gint           x1,
               gint           y1,
               gint           x2,
               gint           y2,
               gint           depth,
               gint           scale_depth)
145
{
146 147
  gfloat tl[4], ml[4], bl[4], mt[4], mm[4], mb[4], tr[4], mr[4], br[4];
  gfloat tmp[4];
148 149 150
  gint    xm, ym;
  gfloat  ran;

151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
  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;

      gegl_buffer_get (context->output, 1.0, &rect, babl_format ("RGBA float"),
                       context->buffer, GEGL_AUTO_ROWSTRIDE);

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

      ret = do_plasma_big (context, x1, y1, x2, y2, depth, scale_depth);

      context->using_buffer = FALSE;

      gegl_buffer_set (context->output, &rect, babl_format ("RGBA float"),
                       context->buffer, GEGL_AUTO_ROWSTRIDE);

      return ret;
    }

181 182 183
  xm = (x1 + x2) / 2;
  ym = (y1 + y2) / 2;

184
//  if (depth == -1)
185
    {
186
      random_rgba (context->gr, tl);
187
      put_pixel (context, tl, x1, y1);
188

189
      random_rgba (context->gr, tr);
190
      put_pixel (context, tr, x2, y1);
191

192
      random_rgba (context->gr, bl);
193
      put_pixel (context, bl, x1, y2);
194

195
      random_rgba (context->gr, br);
196
      put_pixel (context, br, x2, y2);
197

198
      random_rgba (context->gr, mm);
199
      put_pixel (context, mm, xm, ym);
200

201
      random_rgba (context->gr, ml);
202
      put_pixel (context, ml, x1, ym);
203

204
      random_rgba (context->gr, mr);
205
      put_pixel (context, mr, x2, ym);
206

207
      random_rgba (context->gr, mt);
208
      put_pixel (context, mt, xm, y1);
209

210
      random_rgba (context->gr, mb);
211
      put_pixel (context, mb, xm, y2);
212

213
      return FALSE;
214 215
    }

216
  //if (!depth)
217
    {
218 219
      if (x1 == x2 && y1 == y2)
        return FALSE;
220

221
      gegl_buffer_sample (context->output, x1, y1, 1.0, tl, babl_format ("RGBA float"),
222
                          GEGL_INTERPOLATION_NEAREST);
223
      gegl_buffer_sample (context->output, x1, y2, 1.0, bl, babl_format ("RGBA float"),
224
                          GEGL_INTERPOLATION_NEAREST);
225
      gegl_buffer_sample (context->output, x2, y1, 1.0, tr, babl_format ("RGBA float"),
226
                          GEGL_INTERPOLATION_NEAREST);
227
      gegl_buffer_sample (context->output, x2, y2, 1.0, br, babl_format ("RGBA float"),
228
                          GEGL_INTERPOLATION_NEAREST);
229

230
      ran = context->o->turbulence / (2.0 * scale_depth);
231 232

      if (xm != x1 || xm != x2)
233 234 235 236 237 238 239 240 241 242 243 244 245 246
        {
          /* 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);
            }
        }
247 248 249


      if (ym != y1 || ym != x2)
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
        {
          /* 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);
            }
        }
267 268

      if (y1 != y2 || x1 != x2)
269
        {
270
          /* Middle pixel. */
271 272 273
          average_pixel (mm, tl, br);
          average_pixel (tmp, bl, tr);
          average_pixel (mm, mm, tmp);
274

275 276 277
          add_random (context->gr, mm, ran);
          put_pixel (context, mm, xm, ym);
        }
278 279

      return x2 - x1 < 3 && y2 - y1 < 3;
280 281 282 283
    }

  if (x1 < x2 || y1 < y2)
    {
284
      /* Top left. */
285
      do_plasma_big (context, x1, y1, xm, ym, depth - 1, scale_depth + 1);
286
      /* Bottom left. */
287
      do_plasma_big (context, x1, ym, xm, y2, depth - 1, scale_depth + 1);
288
      /* Top right. */
289
      do_plasma_big (context, xm, y1, x2, ym, depth - 1, scale_depth + 1);
290
      /* Bottom right. */
291
      return do_plasma_big (context, xm, ym, x2, y2, depth - 1, scale_depth + 1);
292 293 294 295 296 297
    }

  return TRUE;
}

static GeglRectangle
298
plasma_get_bounding_box (GeglOperation *operation)
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
{
  GeglRectangle  result = {0,0,0,0};
  GeglRectangle *in_rect = gegl_operation_source_get_bounding_box (operation, "input");

  gegl_rectangle_copy (&result, in_rect);

  return result;
}

static gboolean
process (GeglOperation       *operation,
         GeglBuffer          *input,
         GeglBuffer          *output,
         const GeglRectangle *result)
{
314 315
  PlasmaContext *context;
  GeglRectangle boundary;
316 317 318
  gint   depth;
  gint   x, y;

319 320 321
  context = g_new (PlasmaContext, 1);
  context->o = GEGL_CHANT_PROPERTIES (operation);
  context->output = output;
322 323
  context->buffer = g_malloc (TILE_SIZE * TILE_SIZE * 4 * sizeof (float));
  context->using_buffer = FALSE;
324 325 326

  boundary = plasma_get_bounding_box (operation);

327 328 329 330 331 332 333
  /*
   * The first time only puts seed pixels (corners, center of edges,
   * center of image)
   */
  x = boundary.x + boundary.width;
  y = boundary.y + boundary.height;

334 335
  if (context->o->seed == -1)
    context->gr = g_rand_new ();
336
  else
337 338 339
    context->gr = g_rand_new_with_seed (context->o->seed);

  do_plasma_big (context, boundary.x, boundary.y, x-1, y-1, -1, 0);
340

341 342 343
  /*
   * Now we recurse through the images, going deeper each time
   */
344
  depth = 1;
345
  while (!do_plasma_big (context, boundary.x, boundary.y, x-1, y-1, depth, 0))
346
    depth++;
347

348
  gegl_buffer_sample_cleanup (context->output);
349
  g_free (context->buffer);
350 351
  g_free (context);

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
  return TRUE;
}

static GeglRectangle
get_required_for_output (GeglOperation       *operation,
                         const gchar         *input_pad,
                         const GeglRectangle *roi)
{
  GeglRectangle result = *gegl_operation_source_get_bounding_box (operation,
                                                                  "input");
  return result;
}

static GeglRectangle
get_cached_region (GeglOperation       *operation,
                   const GeglRectangle *roi)
{
  return *gegl_operation_source_get_bounding_box (operation, "input");
}

static void
gegl_chant_class_init (GeglChantClass *klass)
{
  GeglOperationClass       *operation_class;
  GeglOperationFilterClass *filter_class;

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

  filter_class->process                    = process;
  operation_class->prepare                 = prepare;
  operation_class->get_required_for_output = get_required_for_output;
  operation_class->get_cached_region       = get_cached_region;

  operation_class->categories  = "render";
  operation_class->name        = "gegl:plasma";
388
  operation_class->description = _("Performs plasma on the image.");
389 390 391
}

#endif