/* GIMP - The GNU 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 3 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, see <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <stdlib.h>
#include <string.h>

#include <gtk/gtk.h>

#include <libgimp/gimp.h>

#include "gimpressionist.h"
#include "brush.h"
#include "orientation.h"
#include "placement.h"
#include "ppmtool.h"
#include "preview.h"
#include "random.h"
#include "size.h"
#include "infile.h"

#include <libgimp/stdplugins-intl.h>

static gimpressionist_vals_t runningvals;

static double
get_siz_from_pcvals (double x, double y)
{
  return getsiz_proto (x, y, pcvals.num_size_vectors, pcvals.size_vectors,
                       pcvals.size_strength_exponent, pcvals.size_voronoi);
}

static int
get_pixel_value (double dir)
{
  while (dir < 0.0)
    dir += 360.0;
  while (dir >= 360.0)
    dir -= 360.0;
  return dir * 255.0 / 360.0;
}

static void
prepare_brush (ppm_t *p)
{
  int x, y;
  int rowstride = p->width * 3;

  for (y = 0; y< p->height; y++)
    {
      for (x = 0; x < p->width; x++)
        {
          p->col[y * rowstride + x * 3 + 1] = 0;
        }
    }

  for (y = 1; y< p->height; y++)
    {
      for (x = 1; x < p->width; x++)
        {
          int v = p->col[y * rowstride + x * 3] -
                  p->col[(y - 1) * rowstride + (x - 1) * 3];
          if (v < 0)
            v = 0;
          p->col[y * rowstride + x * 3 + 1] = v;
        }
    }
}

static double
sum_brush (ppm_t *p)
{
  double sum = 0;
  int i;

  for (i = 0; i < p->width*3*p->height; i += 3)
    sum += p->col[i];
  return sum;
}

/* TODO : Use r = rgb[0]; g = rgb[1] ; b = rgb[2]; instead of
 * the direct references here.
 * */
static int
get_hue (guchar *rgb)
{
  double h, v, temp, diff;
  /* TODO : There seems to be some typos in the comments here.
   * Ask vidar what he meant.
   * */
  if ((rgb[0] == rgb[1]) && (rgb[0] == rgb[2])) /* Gray */
    return 0;
  v = (rgb[0] > rgb[1] ? rgb[0] : rgb[1]);     /* v = st<F8>rste verdi */
  if (rgb[2] > v)
    v = rgb[2];
  temp = (rgb[0] > rgb[1] ? rgb[1] : rgb[0] ); /* temp = minste */
  if (rgb[2] < temp)
    temp = rgb[2];
  diff = v - temp;

  if (v == rgb[0])
    h = ((double)rgb[1] - rgb[2]) / diff;
  else if(v == rgb[1])
    h = ((double)rgb[2] - rgb[0]) / diff + 2;
  else /* v == rgb[2] */
    h = ((double)rgb[0] - rgb[1]) / diff + 4;
  if(h < 0) h += 6;
  return h * 255.0 / 6.0;
}

static int
choose_best_brush (ppm_t *p, ppm_t *a, int tx, int ty,
                   ppm_t *brushes, int num_brushes,
                   double *brushes_sum, int start, int step)
{
  double dev, thissum;
  double bestdev = 0.0;
  double r, g, b;
  int    best = -1;
  int    x, y, h;
  long   i;
  GList *brlist = NULL;

  for (i = start; i < num_brushes; i += step)
    {
      ppm_t *brush = &brushes[i];
#if 0
      thissum = 0.0;
#endif
      thissum = brushes_sum[i];

/* TODO: Pointer-arithmeticize this code */
      r = g = b = 0.0;
      for (y = 0; y < brush->height; y++)
        {
          guchar *row = p->col + (ty + y) * p->width * 3;

          for (x = 0; x < brush->width; x++)
            {
              int    k = (tx + x) * 3;
              double v;

              if ((h = brush->col[(y * brush->width * 3) + x * 3]))
                {
#if 0
                  thissum += h;
#endif
                  v = h / 255.0;
                  r += row[k+0] * v;
                  g += row[k+1] * v;
                  b += row[k+2] * v;
                }
            }
        }
      r = r * 255.0 / thissum;
      g = g * 255.0 / thissum;
      b = b * 255.0 / thissum;

      dev = 0.0;
      for (y = 0; y < brush->height; y++)
        {
          guchar *row = p->col + (ty + y) * p->width * 3;

          for (x = 0; x < brush->width; x++)
            {
              int    k = (tx + x) * 3;
              double v;

              if ((h = brush->col[(y * brush->width * 3) + x * 3]))
                {
                  v = h / 255.0;
                  dev += fabs (row[k+0] - r) * v;
                  dev += fabs (row[k+1] - g) * v;
                  dev += fabs (row[k+2] - b) * v;
                  if (img_has_alpha)
                    dev += a->col[(ty + y) * a->width * 3 + (tx + x) * 3] * v;
                }
            }
        }
      dev /= thissum;

      if ((best == -1) || (dev < bestdev))
        {
          if (brlist)
            g_list_free (brlist);
          brlist = NULL;
        }

      if (dev <= bestdev || best < 0)
        {
          best = i;
          bestdev = dev;
          brlist = g_list_append (brlist, GINT_TO_POINTER (i));
        }
      if (dev < runningvals.devthresh)
        break;
    }

  if (!brlist)
    {
      g_printerr("What!? No brushes?!\n");
      return 0;
    }

  i = g_rand_int_range (random_generator, 0, g_list_length (brlist));
  best = GPOINTER_TO_INT ((g_list_nth (brlist,i))->data);
  g_list_free (brlist);

  return best;
}

static void
apply_brush (ppm_t *brush,
             ppm_t *shadow,
             ppm_t *p, ppm_t *a,
             int tx, int ty, int r, int g, int b)
{
  ppm_t  tmp;
  ppm_t  atmp;
  double v, h;
  int    x, y;
  double edgedarken = 1.0 - runningvals.general_dark_edge;
  double relief = runningvals.brush_relief / 100.0;
  int    shadowdepth = pcvals.general_shadow_depth;
  int    shadowblur = pcvals.general_shadow_blur;

  atmp.col = 0;
  atmp.width = 0;

  tmp = *p;
  if (img_has_alpha)
    atmp = *a;

  if (shadow)
    {
      int sx = tx + shadowdepth - shadowblur * 2;
      int sy = ty + shadowdepth - shadowblur * 2;

      for (y = 0; y < shadow->height; y++)
        {
          guchar *row, *arow = NULL;

          if ((sy + y) < 0)
            continue;
          if ((sy + y) >= tmp.height)
            break;
          row = tmp.col + (sy + y) * tmp.width * 3;

          if (img_has_alpha)
            arow = atmp.col + (sy + y) * atmp.width * 3;

          for (x = 0; x < shadow->width; x++)
            {
              int k = (sx + x) * 3;

              if ((sx + x) < 0)
                continue;
              if ((sx + x) >= tmp.width)
                break;

              h = shadow->col[y * shadow->width * 3 + x * 3 + 2];

              if (!h)
                continue;
              v = 1.0 - (h / 255.0 * runningvals.general_shadow_darkness / 100.0);

              row[k+0] *= v;
              row[k+1] *= v;
              row[k+2] *= v;
              if (img_has_alpha)
                arow[k] *= v;
            }
        }
    }

  for (y = 0; y < brush->height; y++)
    {
      guchar *row = tmp.col + (ty + y) * tmp.width * 3;
      guchar *arow = NULL;

      if (img_has_alpha)
        arow = atmp.col + (ty + y) * atmp.width * 3;

      for (x = 0; x < brush->width; x++)
        {
          int k = (tx + x) * 3;
          h = brush->col[y * brush->width * 3 + x * 3];

          if (!h) continue;

          if (runningvals.color_brushes)
            {
              v = 1.0 - brush->col[y * brush->width * 3 + x * 3 + 2] / 255.0;
              row[k+0] *= v;
              row[k+1] *= v;
              row[k+2] *= v;
              if (img_has_alpha)
                arow[(tx + x) * 3] *= v;
            }
          v = (1.0 - h / 255.0) * edgedarken;
          row[k+0] *= v;
          row[k+1] *= v;
          row[k+2] *= v;
          if(img_has_alpha) arow[k] *= v;

          v = h / 255.0;
          row[k+0] += r * v;
          row[k+1] += g * v;
          row[k+2] += b * v;
        }
    }

  if (relief > 0.001)
    {
      for (y = 1; y < brush->height; y++)
        {
          guchar *row = tmp.col + (ty + y) * tmp.width * 3;

          for (x = 1; x < brush->width; x++)
            {
              int k = (tx + x) * 3;
              h = brush->col[y * brush->width * 3 + x * 3 + 1] * relief;
              if (h < 0.001)
                continue;
              if (h > 255) h = 255;
              row[k+0] = (row[k+0] * (255-h) + 255 * h) / 255;
              row[k+1] = (row[k+1] * (255-h) + 255 * h) / 255;
              row[k+2] = (row[k+2] * (255-h) + 255 * h) / 255;
            }
        }
    }
}

void
repaint (ppm_t *p, ppm_t *a)
{
  int         x, y;
  int         tx = 0, ty = 0;
  ppm_t       tmp = {0, 0, NULL};
  ppm_t       atmp = {0, 0, NULL};
  int         r, g, b, h, i, j, on, sn;
  int         num_brushes, maxbrushwidth, maxbrushheight;
  guchar      back[3] = {0, 0, 0};
  ppm_t      *brushes, *shadows;
  ppm_t      *brush, *shadow = NULL;
  double     *brushes_sum;
  int         cx, cy, maxdist;
  double      scale, relief, startangle, anglespan, density, bgamma;
  int         max_progress;
  ppm_t       paper_ppm = {0, 0, NULL};
  ppm_t       dirmap = {0, 0, NULL};
  ppm_t       sizmap = {0, 0, NULL};
  int        *xpos = NULL, *ypos = NULL;
  int         progstep;
  static int  running = 0;

  int dropshadow = pcvals.general_drop_shadow;
  int shadowblur = pcvals.general_shadow_blur;

  if (running)
    return;
  running++;

  runningvals = pcvals;

  /* Shouldn't be necessary, but... */
  if (img_has_alpha)
    if ((p->width != a->width) || (p->height != a->height))
      {
        g_printerr ("Huh? Image size != alpha size?\n");
        return;
      }

  num_brushes = runningvals.orient_num * runningvals.size_num;
  startangle = runningvals.orient_first;
  anglespan = runningvals.orient_last;

  density = runningvals.brush_density;

  if (runningvals.place_type == PLACEMENT_TYPE_EVEN_DIST)
    density /= 3.0;

  bgamma = runningvals.brushgamma;

  brushes = g_malloc (num_brushes * sizeof (ppm_t));
  brushes_sum = g_malloc (num_brushes * sizeof (double));

  if (dropshadow)
    shadows = g_malloc (num_brushes * sizeof (ppm_t));
  else
    shadows = NULL;

  brushes[0].col = NULL;
  brush_get_selected (&brushes[0]);

  resize (&brushes[0],
          brushes[0].width,
          brushes[0].height * pow (10, runningvals.brush_aspect));
  scale = runningvals.size_last / MAX (brushes[0].width, brushes[0].height);

  if (bgamma != 1.0)
    ppm_apply_gamma (&brushes[0], 1.0 / bgamma, 1,1,1);

  resize (&brushes[0], brushes[0].width * scale, brushes[0].height * scale);
  i = 1 + sqrt (brushes[0].width  * brushes[0].width +
                brushes[0].height * brushes[0].height);
  ppm_pad (&brushes[0], i-brushes[0].width, i-brushes[0].width,
           i - brushes[0].height, i - brushes[0].height, back);

  for (i = 1; i < num_brushes; i++)
    {
      brushes[i].col = NULL;
      ppm_copy (&brushes[0], &brushes[i]);
    }

    for (i = 0; i < runningvals.size_num; i++)
      {
      double sv;
      if (runningvals.size_num > 1)
        sv = i / (runningvals.size_num - 1.0);
      else sv = 1.0;
      for (j = 0; j < runningvals.orient_num; j++)
        {
          h = j + i * runningvals.orient_num;
          free_rotate (&brushes[h],
                       startangle + j * anglespan / runningvals.orient_num);
          rescale (&brushes[h],
                   ( sv      * runningvals.size_first +
                    (1.0-sv) * runningvals.size_last    ) / runningvals.size_last);
          autocrop (&brushes[h],1);
        }
    }

  /* Brush-debugging */
#if 0
  for (i = 0; i < num_brushes; i++)
    {
      char tmp[1000];
      g_snprintf (tmp, sizeof (tmp), "/tmp/_brush%03d.ppm", i);
      ppm_save (&brushes[i], tmp);
    }
#endif

  for (i = 0; i < num_brushes; i++)
    {
      if (!runningvals.color_brushes)
        prepare_brush (&brushes[i]);
      brushes_sum[i] = sum_brush (&brushes[i]);
    }

  brush = &brushes[0];

  maxbrushwidth = maxbrushheight = 0;
  for (i = 0; i < num_brushes; i++)
    {
      if (brushes[i].width > maxbrushwidth)
        maxbrushwidth = brushes[i].width;
      if (brushes[i].height > maxbrushheight)
        maxbrushheight = brushes[i].height;
    }

  for (i = 0; i < num_brushes; i++)
    {
      int xp, yp;
      guchar blk[3] = {0, 0, 0};

      xp = maxbrushwidth - brushes[i].width;
      yp = maxbrushheight - brushes[i].height;
      if (xp || yp)
        ppm_pad (&brushes[i], xp / 2, xp - xp / 2, yp / 2, yp - yp / 2, blk);
    }

  if (dropshadow)
    {
      for (i = 0; i < num_brushes; i++)
        {
          shadows[i].col = NULL;
          ppm_copy (&brushes[i], &shadows[i]);
          ppm_apply_gamma (&shadows[i], 0, 1,1,0);
          ppm_pad (&shadows[i], shadowblur*2, shadowblur*2,
                   shadowblur*2, shadowblur*2, back);
          for (j = 0; j < shadowblur; j++)
            blur (&shadows[i], 2, 2);
#if 0
          autocrop (&shadows[i],1);
#endif
        }
#if 0
      maxbrushwidth += shadowdepth*3;
      maxbrushheight += shadowdepth*3;
#endif
    }

  /* For extra annoying debugging :-) */
#if 0
  ppm_save (brushes, "/tmp/__brush.ppm");
  if (shadows) ppm_save (shadows, "/tmp/__shadow.ppm");
  system ("xv /tmp/__brush.ppm & xv /tmp/__shadow.ppm & ");
#endif

  if (runningvals.general_paint_edges)
    {
      edgepad (p, maxbrushwidth, maxbrushwidth,
               maxbrushheight, maxbrushheight);
      if (img_has_alpha)
        edgepad (a, maxbrushwidth, maxbrushwidth,
                 maxbrushheight, maxbrushheight);
    }

  if (img_has_alpha)
    {
    /* Initially fully transparent */
      if (runningvals.general_background_type == BG_TYPE_TRANSPARENT)
        {
          guchar tmpcol[3] = {255, 255, 255};

          ppm_new (&atmp, a->width, a->height);
          fill (&atmp, tmpcol);
        }
      else
        {
          ppm_copy (a, &atmp);
        }
    }

  if (runningvals.general_background_type == BG_TYPE_SOLID)
    {
      guchar tmpcol[3];

      ppm_new (&tmp, p->width, p->height);
      gegl_color_get_pixel (runningvals.color, babl_format ("R'G'B' u8"), tmpcol);
      fill (&tmp, tmpcol);
    }
  else if (runningvals.general_background_type == BG_TYPE_KEEP_ORIGINAL)
    {
      ppm_copy (p, &tmp);
    }
  else
    {
      int dx, dy;

      ppm_new (&tmp, p->width, p->height);
      ppm_load (runningvals.selected_paper, &paper_ppm);

      if (runningvals.paper_scale != 100.0)
        {
          scale = runningvals.paper_scale / 100.0;
          resize (&paper_ppm, paper_ppm.width * scale, paper_ppm.height * scale);
        }

      if (runningvals.paper_invert)
        ppm_apply_gamma (&paper_ppm, -1.0, 1, 1, 1);

      dx = runningvals.general_paint_edges ? paper_ppm.width - maxbrushwidth : 0;
      dy = runningvals.general_paint_edges ? paper_ppm.height - maxbrushheight : 0;

      for (y = 0; y < tmp.height; y++)
        {
          int lx;
          int ry = (y + dy) % paper_ppm.height;

          for (x = 0; x < tmp.width; x+=lx)
            {
              int rx = (x + dx) % paper_ppm.width;

              lx = MIN (tmp.width - x, paper_ppm.width - rx);

              memcpy (&tmp.col[y * tmp.width * 3 + x * 3],
                      &paper_ppm.col[ry * paper_ppm.width * 3 + rx * 3],
                      3 * lx);
            }
        }
    }

  cx = p->width / 2;
  cy = p->height / 2;
  maxdist = sqrt (cx * cx + cy * cy);

  switch (runningvals.orient_type)
    {
    case ORIENTATION_VALUE:
      ppm_new (&dirmap, p->width, p->height);
      for (y = 0; y < dirmap.height; y++)
        {
          guchar *dstrow = &dirmap.col[y * dirmap.width * 3];
          guchar *srcrow = &p->col[y * p->width * 3];
          for (x = 0; x < dirmap.width; x++)
            {
              dstrow[x * 3] =
                (srcrow[x * 3] + srcrow[x * 3 + 1] + srcrow[x * 3 + 2]) / 3;
            }
        }
      break;

    case ORIENTATION_RADIUS:
      ppm_new (&dirmap, p->width, p->height);
      for (y = 0; y < dirmap.height; y++)
        {
          guchar *dstrow = &dirmap.col[y * dirmap.width * 3];
          double ysqr = (cy - y) * (cy - y);

          for (x = 0; x < dirmap.width; x++)
            {
              dstrow[x*3] = sqrt ((cx - x) * (cx - x) + ysqr) * 255 / maxdist;
            }
        }
      break;

    case ORIENTATION_RADIAL:
      ppm_new (&dirmap, p->width, p->height);
      for (y = 0; y < dirmap.height; y++)
        {
          guchar *dstrow = &dirmap.col[y * dirmap.width * 3];

          for (x = 0; x < dirmap.width; x++)
            {
              dstrow[x * 3] = (G_PI + atan2 (cy - y, cx - x)) *
                              255.0 / (G_PI * 2);
            }
        }
      break;

    case ORIENTATION_FLOWING:
      ppm_new (&dirmap, p->width / 6 + 5, p->height / 6 + 5);
      mkgrayplasma (&dirmap, 15);
      blur (&dirmap, 2, 2);
      blur (&dirmap, 2, 2);
      resize (&dirmap, p->width, p->height);
      blur (&dirmap, 2, 2);
      if (runningvals.general_paint_edges)
        edgepad (&dirmap, maxbrushwidth, maxbrushheight,
                 maxbrushwidth, maxbrushheight);
      break;

    case ORIENTATION_HUE:
      ppm_new (&dirmap, p->width, p->height);
      for (y = 0; y < dirmap.height; y++)
        {
          guchar *dstrow = &dirmap.col[y * dirmap.width * 3];
          guchar *srcrow = &p->col[y * p->width * 3];

          for (x = 0; x < dirmap.width; x++)
            {
              dstrow[x * 3] = get_hue (&srcrow[x * 3]);
            }
        }
      break;

    case ORIENTATION_ADAPTIVE:
      {
        guchar tmpcol[3] = {0, 0, 0};

        ppm_new (&dirmap, p->width, p->height);
        fill (&dirmap, tmpcol);
      }
      break;

    case ORIENTATION_MANUAL:
      ppm_new (&dirmap, p->width-maxbrushwidth*2, p->height-maxbrushheight*2);
      for (y = 0; y < dirmap.height; y++)
        {
          guchar *dstrow = &dirmap.col[y * dirmap.width * 3];
          double tmpy = y / (double)dirmap.height;
          for (x = 0; x < dirmap.width; x++)
            {
              dstrow[x * 3] = get_pixel_value(90 -
                                              get_direction(x /
                                                            (double)dirmap.width,
                                                            tmpy, 1));
            }
        }
      edgepad (&dirmap,
               maxbrushwidth, maxbrushwidth,
               maxbrushheight, maxbrushheight);
    break;
  }

  if (runningvals.size_type == SIZE_TYPE_VALUE)
    {
      ppm_new (&sizmap, p->width, p->height);
      for (y = 0; y < sizmap.height; y++)
        {
          guchar *dstrow = &sizmap.col[y * sizmap.width * 3];
          guchar *srcrow = &p->col[y * p->width * 3];

          for (x = 0; x < sizmap.width; x++)
            {
              dstrow[x * 3] =
                (srcrow[x * 3] + srcrow[x * 3 + 1] + srcrow[x * 3 + 2]) / 3;
            }
        }
    }
  else if (runningvals.size_type == SIZE_TYPE_RADIUS)
    {
      ppm_new (&sizmap, p->width, p->height);
      for (y = 0; y < sizmap.height; y++)
        {
          guchar *dstrow = &sizmap.col[y * sizmap.width * 3];
          double ysqr = (cy - y) * (cy - y);

          for (x = 0; x < sizmap.width; x++)
            {
              dstrow[x * 3] =
                sqrt ((cx - x) * (cx - x) + ysqr) * 255 / maxdist;
            }
        }
    }
  else if (runningvals.size_type == SIZE_TYPE_RADIAL)
    {
      ppm_new (&sizmap, p->width, p->height);
      for (y = 0; y < sizmap.height; y++)
        {
          guchar *dstrow = &sizmap.col[y * sizmap.width * 3];

          for (x = 0; x < sizmap.width; x++)
            {
              dstrow[x * 3] = (G_PI + atan2 (cy - y, cx - x)) *
                              255.0 / (G_PI * 2);
            }
        }
    }
  else if (runningvals.size_type == SIZE_TYPE_FLOWING)
    {
      ppm_new (&sizmap, p->width / 6 + 5, p->height / 6 + 5);
      mkgrayplasma (&sizmap, 15);
      blur (&sizmap, 2, 2);
      blur (&sizmap, 2, 2);
      resize (&sizmap, p->width, p->height);
      blur (&sizmap, 2, 2);
      if (runningvals.general_paint_edges)
        edgepad (&sizmap,
                 maxbrushwidth, maxbrushheight,
                 maxbrushwidth, maxbrushheight);
    }
  else if (runningvals.size_type == SIZE_TYPE_HUE)
    {
      ppm_new (&sizmap, p->width, p->height);
      for (y = 0; y < sizmap.height; y++)
        {
          guchar *dstrow = &sizmap.col[y * sizmap.width * 3];
          guchar *srcrow = &p->col[y * p->width * 3];

          for (x = 0; x < sizmap.width; x++)
            {
              dstrow[ x * 3] = get_hue (&srcrow[x * 3]);
            }
        }
    }
  else if (runningvals.size_type == SIZE_TYPE_ADAPTIVE)
    {
      guchar tmpcol[3] = {0, 0, 0};

      ppm_new (&sizmap, p->width, p->height);
      fill (&sizmap, tmpcol);
    }
  else if (runningvals.size_type == SIZE_TYPE_MANUAL)
    {
      ppm_new (&sizmap,
               p->width-maxbrushwidth * 2,
               p->height-maxbrushheight * 2);

      for (y = 0; y < sizmap.height; y++)
        {
          guchar *dstrow = &sizmap.col[y * sizmap.width * 3];
          double tmpy = y / (double)sizmap.height;

          for (x = 0; x < sizmap.width; x++)
            {
              dstrow[x * 3] = 255 * (1.0 - get_siz_from_pcvals (x / (double)sizmap.width, tmpy));
            }
        }
      edgepad (&sizmap,
               maxbrushwidth, maxbrushwidth,
               maxbrushheight, maxbrushheight);
    }
#if 0
  ppm_save(&sizmap, "/tmp/_sizmap.ppm");
#endif
  if (runningvals.place_type == PLACEMENT_TYPE_RANDOM)
    {
      i = tmp.width * tmp.height / (maxbrushwidth * maxbrushheight);
      i *= density;
    }
  else if (runningvals.place_type == PLACEMENT_TYPE_EVEN_DIST)
    {
      i = (int)(tmp.width * density / maxbrushwidth) *
          (int)(tmp.height * density / maxbrushheight);
#if 0
    g_printerr("i=%d\n", i);
#endif
    }

  if (i < 1)
    i = 1;

  max_progress = i;
  progstep = max_progress / 30;
  if (progstep < 10)
    progstep = 10;

  if (runningvals.place_type == PLACEMENT_TYPE_EVEN_DIST)
    {
      int j;

      xpos = g_new (int, i);
      ypos = g_new (int, i);
      for (j = 0; j < i; j++)
        {
          int factor = (int)(tmp.width * density / maxbrushwidth + 0.5);

          if (factor < 1)
            factor = 1;
          xpos[j] = maxbrushwidth/2 + (j % factor) * maxbrushwidth / density;
          ypos[j] = maxbrushheight/2 + (j / factor) * maxbrushheight / density;
        }
      for (j = 0; j < i; j++)
        {
          int a, b;

          a = g_rand_int_range (random_generator, 0, i);
          b = xpos[j]; xpos[j] = xpos[a]; xpos[a] = b;
          b = ypos[j]; ypos[j] = ypos[a]; ypos[a] = b;
        }
    }

  for (; i; i--)
    {
      int n;
      double thissum;

      if (i % progstep == 0)
        {
          if(runningvals.run)
            {
              gimp_progress_update (0.8 - 0.8 * ((double)i / max_progress));
            }
          else
            {
              char tmps[40];

              g_snprintf (tmps, sizeof (tmps),
                          "%.1f %%", 100 * (1.0 - ((double)i / max_progress)));
              preview_set_button_label (tmps);

              while(gtk_events_pending())
                gtk_main_iteration();
            }
        }

      if (runningvals.place_type == PLACEMENT_TYPE_RANDOM)
        {
          tx = g_rand_int_range (random_generator, maxbrushwidth / 2,
                                 tmp.width - maxbrushwidth / 2);
          ty = g_rand_int_range (random_generator, maxbrushheight / 2,
                                 tmp.height - maxbrushheight / 2);
        }
      else if (runningvals.place_type == PLACEMENT_TYPE_EVEN_DIST)
        {
          tx = xpos[i - 1];
          ty = ypos[i - 1];
        }
      if (runningvals.placement_center)
        {
          double z = g_rand_double_range (random_generator, 0, 0.75);
          tx = tx * (1.0 - z) + tmp.width / 2 * z;
          ty = ty * (1.0 - z) + tmp.height / 2 * z;
        }

      if ((tx < maxbrushwidth / 2)             ||
          (ty < maxbrushwidth / 2)             ||
          (tx + maxbrushwidth / 2 >= p->width) ||
          (ty + maxbrushheight / 2 >= p->height))
        {
#if 0
        g_printerr("Internal Error; invalid coords: (%d,%d) i=%d\n", tx, ty, i);
#endif
          continue;
        }

      if (img_has_alpha)
        {
          if (a->col[ty * a->width * 3 + tx * 3] > 128)
            continue;
        }

      sn = on = 0;

      switch (runningvals.orient_type)
        {
        case ORIENTATION_RANDOM:
          on = g_rand_int_range (random_generator, 0, runningvals.orient_num);
          break;

        case ORIENTATION_VALUE:
        case ORIENTATION_RADIUS:
        case ORIENTATION_RADIAL:
        case ORIENTATION_FLOWING:
        case ORIENTATION_HUE:
        case ORIENTATION_MANUAL:
          on = runningvals.orient_num *
                 dirmap.col[ty * dirmap.width * 3 + tx * 3] / 256;
          break;

        case ORIENTATION_ADAPTIVE:
          break; /* Handled below */

        default:
          g_printerr ("Internal error; Unknown orientationtype\n");
            on = 0;
          break;
        }

      switch (runningvals.size_type)
        {
        case SIZE_TYPE_RANDOM:
          sn = g_rand_int_range (random_generator, 0, runningvals.size_num);
          break;

        case SIZE_TYPE_VALUE:
        case SIZE_TYPE_RADIUS:
        case SIZE_TYPE_RADIAL:
        case SIZE_TYPE_FLOWING:
        case SIZE_TYPE_HUE:
        case SIZE_TYPE_MANUAL:
          sn = runningvals.size_num * sizmap.col[ty*sizmap.width*3+tx*3] / 256;
          break;

        case SIZE_TYPE_ADAPTIVE:
          break; /* Handled below */

        default:
          g_printerr ("Internal error; Unknown size_type\n");
          sn = 0;
          break;
      }

      /* Handle Adaptive selections */
      if (runningvals.orient_type == ORIENTATION_ADAPTIVE)
        {
          if (runningvals.size_type == SIZE_TYPE_ADAPTIVE)
            n = choose_best_brush (p, a, tx-maxbrushwidth/2,
                                  ty-maxbrushheight/2, brushes,
                                  num_brushes, brushes_sum, 0, 1);
          else
            {
              int st = sn * runningvals.orient_num;
              n = choose_best_brush (p, a, tx-maxbrushwidth/2,
                                    ty-maxbrushheight/2, brushes,
                                    st+runningvals.orient_num, brushes_sum,
                                    st, 1);
            }
        }
      else
        {
          if (runningvals.size_type == SIZE_TYPE_ADAPTIVE)
            n = choose_best_brush (p, a, tx-maxbrushwidth/2,
                                    ty-maxbrushheight/2, brushes,
                                    num_brushes, brushes_sum,
                                    on, runningvals.orient_num);
          else
            n = sn * runningvals.orient_num + on;
        }
      /* Should never happen, but hey... */
      if (n < 0)
        n = 0;
      else if (n >= num_brushes)
        n = num_brushes - 1;

      tx -= maxbrushwidth/2;
      ty -= maxbrushheight/2;

      brush = &brushes[n];
      if (dropshadow)
        shadow = &shadows[n];
      thissum = brushes_sum[n];

      /* Calculate color - avg. of in-brush pixels */
      if (runningvals.color_type == 0)
        {
          r = g = b = 0;
          for (y = 0; y < brush->height; y++)
            {
              guchar *row = &p->col[(ty + y) * p->width * 3];

              for (x = 0; x < brush->width; x++)
                {
                  int k = (tx + x) * 3;
                  double v;

                  if ((h = brush->col[y * brush->width * 3 + x * 3]))
                    {
                      v = h / 255.0;
                      r += row[k+0] * v;
                      g += row[k+1] * v;
                      b += row[k+2] * v;
                    }
                }
            }
          r = r * 255.0 / thissum;
          g = g * 255.0 / thissum;
          b = b * 255.0 / thissum;
        }
      else if (runningvals.color_type == 1)
        {
          guchar *pixel;

          y = ty + (brush->height / 2);
          x = tx + (brush->width / 2);
          pixel = &p->col[y*p->width * 3 + x * 3];
          r = pixel[0];
          g = pixel[1];
          b = pixel[2];
        }
      else
        {
          /* No such color_type! */
          r = g = b = 0;
        }
      if (runningvals.color_noise > 0.0)
        {
          double v = runningvals.color_noise;
#define BOUNDS(a) (((a) < 0) ? (0) : ((a) > 255) ? 255 : (a))
#define MYASSIGN(a) \
    { \
        a = a + g_rand_double_range (random_generator, -v/2.0, v/2.0); \
        a = BOUNDS(a) ;       \
    }
          MYASSIGN (r);
          MYASSIGN (g);
          MYASSIGN (b);
#undef BOUNDS
#undef MYASSIGN
        }

      apply_brush (brush, shadow, &tmp, &atmp, tx,ty, r,g,b);

      if (runningvals.general_tileable && runningvals.general_paint_edges)
        {
          int orig_width = tmp.width - 2 * maxbrushwidth;
          int orig_height = tmp.height - 2 * maxbrushheight;
          int dox = 0, doy = 0;

          if (tx < maxbrushwidth)
            {
              apply_brush (brush, shadow, &tmp, &atmp, tx+orig_width,ty, r,g,b);
              dox = -1;
            }
          else if (tx > orig_width)
            {
              apply_brush (brush, shadow, &tmp, &atmp, tx-orig_width,ty, r,g,b);
              dox = 1;
            }
          if (ty < maxbrushheight)
            {
              apply_brush (brush, shadow, &tmp, &atmp, tx,ty+orig_height, r,g,b);
              doy = 1;
            }
          else if (ty > orig_height)
            {
              apply_brush (brush, shadow, &tmp, &atmp, tx,ty-orig_height, r,g,b);
              doy = -1;
            }
          if (doy)
            {
              if (dox < 0)
                apply_brush (brush, shadow, &tmp, &atmp,
                             tx+orig_width, ty + doy * orig_height, r, g, b);
              if (dox > 0)
                apply_brush (brush, shadow, &tmp, &atmp,
                             tx-orig_width, ty + doy * orig_height, r, g, b);
            }
        }
    }
  for (i = 0; i < num_brushes; i++)
    {
      ppm_kill (&brushes[i]);
    }
  g_free (brushes);
  g_free (shadows);
  g_free (brushes_sum);

  g_free (xpos);
  g_free (ypos);

  if (runningvals.general_paint_edges)
    {
      crop (&tmp,
            maxbrushwidth, maxbrushheight,
            tmp.width - maxbrushwidth, tmp.height - maxbrushheight);
      if (img_has_alpha)
        crop (&atmp,
              maxbrushwidth, maxbrushheight,
              atmp.width - maxbrushwidth, atmp.height - maxbrushheight);
    }

  ppm_kill (p);
  p->width = tmp.width;
  p->height = tmp.height;
  p->col = tmp.col;

  if (img_has_alpha)
    {
      ppm_kill (a);
      a->width = atmp.width;
      a->height = atmp.height;
      a->col = atmp.col;
    }

  relief = runningvals.paper_relief / 100.0;
  if (relief > 0.001)
    {
      scale = runningvals.paper_scale / 100.0;

      if (PPM_IS_INITED (&paper_ppm))
        {
          tmp = paper_ppm;
          paper_ppm.col = NULL;
        }
      else
        {
          tmp.col = NULL;
          ppm_load (runningvals.selected_paper, &tmp);
          resize (&tmp, tmp.width * scale, tmp.height * scale);
          if (runningvals.paper_invert)
            ppm_apply_gamma (&tmp, -1.0, 1,1,1);
        }
      for (x = 0; x < p->width; x++)
        {
          double h, v;
          int    px = x % tmp.width, py;

          for (y = 0; y < p->height; y++)
            {
              int k = y * p->width * 3 + x * 3;

              py = y % tmp.height;
              if (runningvals.paper_overlay)
                h = (tmp.col[py * tmp.width * 3 + px * 3]-128) * relief;
              else
                h = (tmp.col[py * tmp.width * 3 + px * 3] -
                     (int)tmp.col[((py + 1) % tmp.height) * tmp.width * 3 +
                                  ((px + 1) % tmp.width) * 3]) /
                     -2.0 * relief;
              if (h <= 0.0)
                {
                  v = 1.0 + h/128.0;
                  if (v < 0.0)
                    v = 0.0;
                  else if (v > 1.0)
                    v = 1.0;
                  p->col[k+0] *= v;
                  p->col[k+1] *= v;
                  p->col[k+2] *= v;
                }
              else
                {
                  v = h/128.0;
                  if (v < 0.0)
                    v = 0.0;
                  else if (v > 1.0)
                    v = 1.0;
                  p->col[k+0] = p->col[k+0] * (1.0-v) + 255 * v;
                  p->col[k+1] = p->col[k+1] * (1.0-v) + 255 * v;
                  p->col[k+2] = p->col[k+2] * (1.0-v) + 255 * v;
                }
            }
        }
      ppm_kill (&tmp);
    }

  ppm_kill (&paper_ppm);
  ppm_kill (&dirmap);
  ppm_kill (&sizmap);

  if (runningvals.run)
    {
      gimp_progress_update (0.8);
    }
  else
    {
      preview_set_button_label (_("_Update"));
    }

  running = 0;
}
