/* The GIMP -- an 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 2 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "config.h"

#include <string.h>

#include <glib-object.h>

#include "libgimpcolor/gimpcolor.h"
#include "libgimpmath/gimpmath.h"

#include "paint-funcs-types.h"

#include "base/pixel-processor.h"
#include "base/pixel-region.h"
#include "base/temp-buf.h"
#include "base/tile-manager.h"
#include "base/tile.h"

#include "composite/gimp-composite.h"

#include "paint-funcs.h"
#include "paint-funcs-generic.h"


#define RANDOM_SEED   314159265
#define EPSILON       0.0001


typedef enum
{
  MinifyX_MinifyY,
  MinifyX_MagnifyY,
  MagnifyX_MinifyY,
  MagnifyX_MagnifyY
} ScaleType;


/*  Layer modes information  */
typedef struct _LayerMode LayerMode;
struct _LayerMode
{
  guint   affect_alpha     : 1; /*  does the layer mode affect the alpha channel  */
  guint   increase_opacity : 1; /*  layer mode can increase opacity */
  guint   decrease_opacity : 1; /*  layer mode can decrease opacity */
};

static const LayerMode layer_modes[] =
                               /* This must obviously be in the same
                                * order as the corresponding values
                                 * in the GimpLayerModeEffects enumeration.
                                */
{
  { TRUE,  TRUE,  FALSE, },  /*  GIMP_NORMAL_MODE        */
  { TRUE,  TRUE,  FALSE, },  /*  GIMP_DISSOLVE_MODE      */
  { TRUE,  TRUE,  FALSE, },  /*  GIMP_BEHIND_MODE        */
  { FALSE, FALSE, FALSE, },  /*  GIMP_MULTIPLY_MODE      */
  { FALSE, FALSE, FALSE, },  /*  GIMP_SCREEN_MODE        */
  { FALSE, FALSE, FALSE, },  /*  GIMP_OVERLAY_MODE       */
  { FALSE, FALSE, FALSE, },  /*  GIMP_DIFFERENCE_MODE    */
  { FALSE, FALSE, FALSE, },  /*  GIMP_ADDITION_MODE      */
  { FALSE, FALSE, FALSE, },  /*  GIMP_SUBTRACT_MODE      */
  { FALSE, FALSE, FALSE, },  /*  GIMP_DARKEN_ONLY_MODE   */
  { FALSE, FALSE, FALSE, },  /*  GIMP_LIGHTEN_ONLY_MODE  */
  { FALSE, FALSE, FALSE, },  /*  GIMP_HUE_MODE           */
  { FALSE, FALSE, FALSE, },  /*  GIMP_SATURATION_MODE    */
  { FALSE, FALSE, FALSE, },  /*  GIMP_COLOR_MODE         */
  { FALSE, FALSE, FALSE, },  /*  GIMP_VALUE_MODE         */
  { FALSE, FALSE, FALSE, },  /*  GIMP_DIVIDE_MODE        */
  { FALSE, FALSE, FALSE, },  /*  GIMP_DODGE_MODE         */
  { FALSE, FALSE, FALSE, },  /*  GIMP_BURN_MODE          */
  { FALSE, FALSE, FALSE, },  /*  GIMP_HARDLIGHT_MODE     */
  { FALSE, FALSE, FALSE, },  /*  GIMP_SOFTLIGHT_MODE     */
  { FALSE, FALSE, FALSE, },  /*  GIMP_GRAIN_EXTRACT_MODE */
  { FALSE, FALSE, FALSE, },  /*  GIMP_GRAIN_MERGE_MODE   */
  { TRUE,  FALSE, TRUE,  },  /*  GIMP_COLOR_ERASE_MODE   */
  { TRUE,  FALSE, TRUE,  },  /*  GIMP_ERASE_MODE         */
  { TRUE,  TRUE,  TRUE,  },  /*  GIMP_REPLACE_MODE       */
  { TRUE,  TRUE,  FALSE, }   /*  GIMP_ANTI_ERASE_MODE    */
};

typedef void (* LayerModeFunc) (struct apply_layer_mode_struct *);
static LayerModeFunc layer_mode_funcs[] =
{
  layer_normal_mode,
  layer_dissolve_mode,
  layer_behind_mode,
  layer_multiply_mode,
  layer_screen_mode,
  layer_overlay_mode,
  layer_difference_mode,
  layer_addition_mode,
  layer_subtract_mode,
  layer_darken_only_mode,
  layer_lighten_only_mode,
  layer_hue_mode,
  layer_saturation_mode,
  layer_color_mode,
  layer_value_mode,
  layer_divide_mode,
  layer_dodge_mode,
  layer_burn_mode,
  layer_hardlight_mode,
  layer_softlight_mode,
  layer_grain_extract_mode,
  layer_grain_merge_mode,
  layer_color_erase_mode,
  layer_erase_mode,
  layer_replace_mode,
  layer_anti_erase_mode
};


static const guchar  no_mask = OPAQUE_OPACITY;


/*  Local function prototypes  */

static gint *   make_curve               (gdouble        sigma,
                                          guint         *length);
static gdouble  cubic                    (gdouble         dx,
                                          gint            jm1,
                                          gint            j,
                                          gint            jp1,
                                          gint            jp2);
static void     apply_layer_mode_replace (guchar         *src1,
                                          guchar         *src2,
                                          guchar         *dest,
                                          guchar         *mask,
                                          gint            x,
                                          gint            y,
                                          guint           opacity,
                                          guint           length,
                                          guint           bytes1,
                                          guint           bytes2,
                                          const gboolean *affect);

static inline void rotate_pointers       (guchar         **p,
                                          guint32          n);



static void
update_tile_rowhints (Tile *tile,
                      gint  ymin,
                      gint  ymax)
{
  gint         bpp, ewidth;
  gint         x, y;
  guchar      *ptr;
  guchar       alpha;
  TileRowHint  thishint;

#ifdef HINTS_SANITY
  g_assert (tile != NULL);
#endif

  tile_sanitize_rowhints (tile);

  bpp = tile_bpp (tile);
  ewidth = tile_ewidth (tile);

  if (bpp == 1 || bpp == 3)
    {
      for (y = ymin; y <= ymax; y++)
        tile_set_rowhint (tile, y, TILEROWHINT_OPAQUE);

      return;
    }

  if (bpp == 4)
    {
#ifdef HINTS_SANITY
      g_assert (tile != NULL);
#endif

      ptr = tile_data_pointer (tile, 0, ymin);

#ifdef HINTS_SANITY
      g_assert (ptr != NULL);
#endif

      for (y = ymin; y <= ymax; y++)
        {
          thishint = tile_get_rowhint (tile, y);

#ifdef HINTS_SANITY
          if (thishint == TILEROWHINT_BROKEN)
            g_error ("BROKEN y=%d", y);
          if (thishint == TILEROWHINT_OUTOFRANGE)
            g_error ("OOR y=%d", y);
          if (thishint == TILEROWHINT_UNDEFINED)
            g_error ("UNDEFINED y=%d - bpp=%d ew=%d eh=%d",
                     y, bpp, ewidth, eheight);
#endif

#ifdef HINTS_SANITY
          if (thishint == TILEROWHINT_TRANSPARENT ||
              thishint == TILEROWHINT_MIXED ||
              thishint == TILEROWHINT_OPAQUE)
            {
              goto next_row4;
            }

          if (thishint != TILEROWHINT_UNKNOWN)
            {
              g_error ("MEGABOGUS y=%d - bpp=%d ew=%d eh=%d",
                       y, bpp, ewidth, eheight);
            }
#endif

          if (thishint == TILEROWHINT_UNKNOWN)
            {
              alpha = ptr[3];

              /* row is all-opaque or all-transparent? */
              if (alpha == 0 || alpha == 255)
                {
                  if (ewidth > 1)
                    {
                      for (x = 1; x < ewidth; x++)
                        {
                          if (ptr[x * 4 + 3] != alpha)
                            {
                              tile_set_rowhint (tile, y, TILEROWHINT_MIXED);
                              goto next_row4;
                            }
                        }
                    }
                  tile_set_rowhint (tile, y,
                                    (alpha == 0) ?
                                    TILEROWHINT_TRANSPARENT :
                                    TILEROWHINT_OPAQUE);
                }
              else
                {
                  tile_set_rowhint (tile, y, TILEROWHINT_MIXED);
                }
            }

        next_row4:
          ptr += 4 * ewidth;
        }

      return;
    }

  if (bpp == 2)
    {
#ifdef HINTS_SANITY
      g_assert (tile != NULL);
#endif

      ptr = tile_data_pointer (tile, 0, ymin);

#ifdef HINTS_SANITY
      g_assert (ptr != NULL);
#endif

      for (y = ymin; y <= ymax; y++)
        {
          thishint = tile_get_rowhint (tile, y);

#ifdef HINTS_SANITY
          if (thishint == TILEROWHINT_BROKEN)
            g_error ("BROKEN y=%d",y);
          if (thishint == TILEROWHINT_OUTOFRANGE)
            g_error ("OOR y=%d",y);
          if (thishint == TILEROWHINT_UNDEFINED)
            g_error ("UNDEFINED y=%d - bpp=%d ew=%d eh=%d",
                     y, bpp, ewidth, eheight);
#endif

#ifdef HINTS_SANITY
          if (thishint == TILEROWHINT_TRANSPARENT ||
              thishint == TILEROWHINT_MIXED ||
              thishint == TILEROWHINT_OPAQUE)
            {
              goto next_row2;
            }

          if (thishint != TILEROWHINT_UNKNOWN)
            {
              g_error ("MEGABOGUS y=%d - bpp=%d ew=%d eh=%d",
                       y, bpp, ewidth, eheight);
            }
#endif

          if (thishint == TILEROWHINT_UNKNOWN)
            {
              alpha = ptr[1];

              /* row is all-opaque or all-transparent? */
              if (alpha == 0 || alpha == 255)
                {
                  if (ewidth > 1)
                    {
                      for (x = 1; x < ewidth; x++)
                        {
                          if (ptr[x*2 + 1] != alpha)
                            {
                              tile_set_rowhint (tile, y, TILEROWHINT_MIXED);
                              goto next_row2;
                            }
                        }
                    }
                  tile_set_rowhint (tile, y,
                                    (alpha == 0) ?
                                    TILEROWHINT_TRANSPARENT :
                                    TILEROWHINT_OPAQUE);
                }
              else
                {
                  tile_set_rowhint (tile, y, TILEROWHINT_MIXED);
                }
            }

        next_row2:
          ptr += 2 * ewidth;
        }

      return;
    }

  g_warning ("update_tile_rowhints: Don't know about tiles with bpp==%d", bpp);
}


/*
 * The equations: g(r) = exp (- r^2 / (2 * sigma^2))
 *                   r = sqrt (x^2 + y ^2)
 */

static gint *
make_curve (gdouble  sigma,
            guint   *length)
{
  gint    *curve;
  gdouble  sigma2;
  gdouble  l;
  gint     temp;
  gint     i, n;

  sigma2 = 2 * sigma * sigma;
  l = sqrt (-sigma2 * log (1.0 / 255.0));

  n = ceil (l) * 2;
  if ((n % 2) == 0)
    n += 1;

  curve = g_new (gint, n);

  *length = n / 2;
  curve += *length;
  curve[0] = 255;

  for (i = 1; i <= *length; i++)
    {
      temp = (gint) (exp (- (i * i) / sigma2) * 255);
      curve[-i] = temp;
      curve[i] = temp;
    }

  return curve;
}


static inline void
run_length_encode (const guchar *src,
                   guint        *dest,
                   guint         w,
                   guint         bytes)
{
  guint  start;
  guint  i;
  guint  j;
  guchar last;

  last = *src;
  src += bytes;
  start = 0;

  for (i = 1; i < w; i++)
    {
      if (*src != last)
        {
          for (j = start; j < i; j++)
            {
              *dest++ = (i - j);
              *dest++ = last;
            }
          start = i;
          last = *src;
        }
      src += bytes;
    }

  for (j = start; j < i; j++)
    {
      *dest++ = (i - j);
      *dest++ = last;
    }
}

/* Note: cubic function no longer clips result */
static inline gdouble
cubic (gdouble dx,
       gint    jm1,
       gint    j,
       gint    jp1,
       gint    jp2)
{
  /* Catmull-Rom - not bad */
  return (gdouble) ((( ( - jm1 + 3 * j - 3 * jp1 + jp2 ) * dx +
                       ( 2 * jm1 - 5 * j + 4 * jp1 - jp2 ) ) * dx +
                     ( - jm1 + jp1 ) ) * dx + (j + j) ) / 2.0;
}

/*********************/
/*  FUNCTIONS        */
/*********************/

void
paint_funcs_setup (void)
{
  GRand *gr;
  gint   i;

  /*  generate a table of random seeds  */
  gr = g_rand_new_with_seed (RANDOM_SEED);

  for (i = 0; i < RANDOM_TABLE_SIZE; i++)
    random_table[i] = g_rand_int (gr);

  for (i = 0; i < 256; i++)
    add_lut[i] = i;

  for (i = 256; i <= 510; i++)
    add_lut[i] = 255;

  g_rand_free (gr);
}

void
paint_funcs_free (void)
{
}

void
combine_indexed_and_indexed_pixels (const guchar   *src1,
                                    const guchar   *src2,
                                    guchar         *dest,
                                    const guchar   *mask,
                                    guint           opacity,
                                    const gboolean *affect,
                                    guint           length,
                                    guint           bytes)
{
  gint          b;
  guchar        new_alpha;
  const guchar *m;
  gint          tmp;

  if (mask)
    {
      m = mask;
      while (length --)
        {
          new_alpha = INT_MULT(*m , opacity, tmp);

          for (b = 0; b < bytes; b++)
            dest[b] = (affect[b] && new_alpha > 127) ? src2[b] : src1[b];

          m++;

          src1 += bytes;
          src2 += bytes;
          dest += bytes;
        }
    }
  else
    {
      while (length --)
        {
          new_alpha = opacity;

          for (b = 0; b < bytes; b++)
            dest[b] = (affect[b] && new_alpha > 127) ? src2[b] : src1[b];

          src1 += bytes;
          src2 += bytes;
          dest += bytes;
        }
    }
}


void
combine_indexed_and_indexed_a_pixels (const guchar   *src1,
                                      const guchar   *src2,
                                      guchar         *dest,
                                      const guchar   *mask,
                                      guint           opacity,
                                      const gboolean *affect,
                                      guint           length,
                                      guint           bytes)
{
  gint   b, alpha;
  guchar new_alpha;
  gint   src2_bytes;
  glong  tmp;
  const guchar *m;

  alpha = 1;
  src2_bytes = 2;

  if (mask)
    {
      m = mask;
      while (length --)
        {
          new_alpha = INT_MULT3(src2[alpha], *m, opacity, tmp);

          for (b = 0; b < bytes; b++)
            dest[b] = (affect[b] && new_alpha > 127) ? src2[b] : src1[b];

          m++;

          src1 += bytes;
          src2 += src2_bytes;
          dest += bytes;
        }
    }
  else
    {
      while (length --)
        {
          new_alpha = INT_MULT(src2[alpha], opacity, tmp);

          for (b = 0; b < bytes; b++)
            dest[b] = (affect[b] && new_alpha > 127) ? src2[b] : src1[b];

          src1 += bytes;
          src2 += src2_bytes;
          dest += bytes;
        }
    }
}


void
combine_indexed_a_and_indexed_a_pixels (const guchar   *src1,
                                        const guchar   *src2,
                                        guchar         *dest,
                                        const guchar   *mask,
                                        guint           opacity,
                                        const gboolean *affect,
                                        guint           length,
                                        guint           bytes)
{
  const guchar * m;
  gint   b, alpha;
  guchar new_alpha;
  glong  tmp;

  alpha = 1;

  if (mask)
    {
      m = mask;

      while (length --)
        {
          new_alpha = INT_MULT3(src2[alpha], *m, opacity, tmp);

          for (b = 0; b < alpha; b++)
            dest[b] = (affect[b] && new_alpha > 127) ? src2[b] : src1[b];

          dest[alpha] = (affect[alpha] && new_alpha > 127) ?
            OPAQUE_OPACITY : src1[alpha];

          m++;

          src1 += bytes;
          src2 += bytes;
          dest += bytes;
        }
    }
  else
    {
      while (length --)
        {
          new_alpha = INT_MULT(src2[alpha], opacity, tmp);

          for (b = 0; b < alpha; b++)
            dest[b] = (affect[b] && new_alpha > 127) ? src2[b] : src1[b];

          dest[alpha] = (affect[alpha] && new_alpha > 127) ?
            OPAQUE_OPACITY : src1[alpha];

          src1 += bytes;
          src2 += bytes;
          dest += bytes;
        }
    }
}


void
combine_inten_a_and_indexed_a_pixels (const guchar *src1,
                                      const guchar *src2,
                                      guchar       *dest,
                                      const guchar *mask,
                                      const guchar *cmap,
                                      guint         opacity,
                                      guint         length,
                                      guint         bytes)
{
  gint   b, alpha;
  guchar new_alpha;
  gint   src2_bytes;
  gint   index;
  glong  tmp;
  const guchar *m;

  alpha = 1;
  src2_bytes = 2;

  if (mask)
    {
      m = mask;

      while (length --)
        {
          new_alpha = INT_MULT3(src2[alpha], *m, opacity, tmp);

          index = src2[0] * 3;

          for (b = 0; b < bytes-1; b++)
            dest[b] = (new_alpha > 127) ? cmap[index + b] : src1[b];

          dest[b] = (new_alpha > 127) ? OPAQUE_OPACITY : src1[b];
          /*  alpha channel is opaque  */

          m++;

          src1 += bytes;
          src2 += src2_bytes;
          dest += bytes;
        }
    }
  else
    {
      while (length --)
        {
          new_alpha = INT_MULT(src2[alpha], opacity, tmp);

          index = src2[0] * 3;

          for (b = 0; b < bytes-1; b++)
            dest[b] = (new_alpha > 127) ? cmap[index + b] : src1[b];

          dest[b] = (new_alpha > 127) ? OPAQUE_OPACITY : src1[b];
          /*  alpha channel is opaque  */

          /* m++; /Per */

          src1 += bytes;
          src2 += src2_bytes;
          dest += bytes;
        }
    }
}


void
combine_inten_and_inten_pixels (const guchar   *src1,
                                const guchar   *src2,
                                guchar         *dest,
                                const guchar   *mask,
                                guint           opacity,
                                const gboolean *affect,
                                guint           length,
                                guint           bytes)
{
  const guchar * m;
  gint   b;
  guchar new_alpha;
  gint   tmp;

  if (mask)
    {
      m = mask;
      while (length --)
        {
          new_alpha = INT_MULT(*m, opacity, tmp);

          for (b = 0; b < bytes; b++)
            dest[b] = (affect[b]) ?
              INT_BLEND(src2[b], src1[b], new_alpha, tmp) :
            src1[b];

          m++;

          src1 += bytes;
          src2 += bytes;
          dest += bytes;
        }
    }
  else
    {
      while (length --)
        {

          for (b = 0; b < bytes; b++)
            dest[b] = (affect[b]) ?
              INT_BLEND(src2[b], src1[b], opacity, tmp) :
            src1[b];

          src1 += bytes;
          src2 += bytes;
          dest += bytes;
        }
    }
}


void
combine_inten_and_inten_a_pixels (const guchar   *src1,
                                  const guchar   *src2,
                                  guchar         *dest,
                                  const guchar   *mask,
                                  guint           opacity,
                                  const gboolean *affect,
                                  guint           length,
                                  guint           bytes)
{
  gint   alpha, b;
  gint   src2_bytes;
  guchar new_alpha;
  const guchar   *m;
  register glong  t1;

  alpha = bytes;
  src2_bytes = bytes + 1;

  if (mask)
    {
      m = mask;
      while (length --)
        {
          new_alpha = INT_MULT3(src2[alpha], *m, opacity, t1);

          for (b = 0; b < bytes; b++)
            dest[b] = (affect[b]) ?
              INT_BLEND(src2[b], src1[b], new_alpha, t1) :
              src1[b];

          m++;
          src1 += bytes;
          src2 += src2_bytes;
          dest += bytes;
        }
    }
  else
    {
      if (bytes == 3 && affect[0] && affect[1] && affect[2])
        while (length --)
        {
          new_alpha = INT_MULT(src2[alpha],opacity,t1);
          dest[0] = INT_BLEND(src2[0] , src1[0] , new_alpha, t1);
          dest[1] = INT_BLEND(src2[1] , src1[1] , new_alpha, t1);
          dest[2] = INT_BLEND(src2[2] , src1[2] , new_alpha, t1);
          src1 += bytes;
          src2 += src2_bytes;
          dest += bytes;
        }
      else
        while (length --)
        {
          new_alpha = INT_MULT(src2[alpha],opacity,t1);
          for (b = 0; b < bytes; b++)
            dest[b] = (affect[b]) ?
              INT_BLEND(src2[b] , src1[b] , new_alpha, t1) :
            src1[b];

          src1 += bytes;
          src2 += src2_bytes;
          dest += bytes;
        }
    }
}

/*orig #define alphify(src2_alpha,new_alpha) \
        if (new_alpha == 0 || src2_alpha == 0)                                                        \
          {                                                                                        \
            for (b = 0; b < alpha; b++)                                                                \
              dest[b] = src1 [b];                                                                \
          }                                                                                        \
        else if (src2_alpha == new_alpha){                                                        \
          for (b = 0; b < alpha; b++)                                                                \
            dest [b] = affect [b] ? src2 [b] : src1 [b];                                        \
        } else {                                                                                \
          ratio = (float) src2_alpha / new_alpha;                                                \
          compl_ratio = 1.0 - ratio;                                                                \
                                                                                                  \
          for (b = 0; b < alpha; b++)                                                                \
            dest[b] = affect[b] ?                                                                \
              (guchar) (src2[b] * ratio + src1[b] * compl_ratio + EPSILON) : src1[b];        \
        }*/

/*shortened #define alphify(src2_alpha,new_alpha) \
        if (src2_alpha != 0 && new_alpha != 0)                                                        \
          {                                                                                        \
            if (src2_alpha == new_alpha){                                                        \
              for (b = 0; b < alpha; b++)                                                        \
              dest [b] = affect [b] ? src2 [b] : src1 [b];                                        \
            } else {                                                                                \
              ratio = (float) src2_alpha / new_alpha;                                                \
              compl_ratio = 1.0 - ratio;                                                        \
                                                                                                  \
              for (b = 0; b < alpha; b++)                                                        \
                dest[b] = affect[b] ?                                                                \
                  (guchar) (src2[b] * ratio + src1[b] * compl_ratio + EPSILON) : src1[b];\
            }                                                                                   \
          }*/

#define alphify(src2_alpha,new_alpha) \
        if (src2_alpha != 0 && new_alpha != 0)                                                        \
          {                                                                                        \
            b = alpha; \
            if (src2_alpha == new_alpha){                                                        \
              do { \
              b--; dest [b] = affect [b] ? src2 [b] : src1 [b];} while (b);        \
            } else {                                                                                \
              ratio = (float) src2_alpha / new_alpha;                                                \
              compl_ratio = 1.0 - ratio;                                                        \
                                                                                                  \
              do { b--; \
                dest[b] = affect[b] ?                                                                \
                  (guchar) (src2[b] * ratio + src1[b] * compl_ratio + EPSILON) : src1[b];\
                   } while (b); \
            }    \
          }

/*special #define alphify4(src2_alpha,new_alpha) \
        if (src2_alpha != 0 && new_alpha != 0)                                                        \
          {                                                                                        \
            if (src2_alpha == new_alpha){                                                        \
              dest [0] = affect [0] ? src2 [0] : src1 [0];                                        \
              dest [1] = affect [1] ? src2 [1] : src1 [1];                                        \
              dest [2] = affect [2] ? src2 [2] : src1 [2];                                        \
            } else {                                                                                \
              ratio = (float) src2_alpha / new_alpha;                                                \
              compl_ratio = 1.0 - ratio;                                                        \
                                                                                                  \
              dest[0] = affect[0] ?                                                                \
                (guchar) (src2[0] * ratio + src1[0] * compl_ratio + EPSILON) : src1[0];  \
              dest[1] = affect[1] ?                                                                \
                (guchar) (src2[1] * ratio + src1[1] * compl_ratio + EPSILON) : src1[1];  \
              dest[2] = affect[2] ?                                                                \
                (guchar) (src2[2] * ratio + src1[2] * compl_ratio + EPSILON) : src1[2];  \
            }                                                                                   \
          }*/

void
combine_inten_a_and_inten_pixels (const guchar   *src1,
                                  const guchar   *src2,
                                  guchar         *dest,
                                  const guchar   *mask,
                                  guint           opacity,
                                  const gboolean *affect,
                                  gboolean        mode_affect,  /*  how does the combination mode affect alpha?  */
                                  guint           length,
                                  guint           bytes)        /*  4 or 2 depending on RGBA or GRAYA  */
{
  gint          alpha, b;
  gint          src2_bytes;
  guchar        src2_alpha;
  guchar        new_alpha;
  const guchar *m;
  gfloat        ratio, compl_ratio;
  glong         tmp;

  src2_bytes = bytes - 1;
  alpha = bytes - 1;

  if (mask)
    {
      m = mask;
      if (opacity == OPAQUE_OPACITY) /* HAS MASK, FULL OPACITY */
        {
          while (length--)
            {
              src2_alpha = *m;
              new_alpha = src1[alpha] +
                INT_MULT((255 - src1[alpha]), src2_alpha, tmp);
              alphify (src2_alpha, new_alpha);

              if (mode_affect)
                {
                  dest[alpha] = (affect[alpha]) ? new_alpha : src1[alpha];
                }
              else
                {
                  dest[alpha] = (src1[alpha]) ? src1[alpha] :
                    (affect[alpha] ? new_alpha : src1[alpha]);
                }

              m++;
              src1 += bytes;
              src2 += src2_bytes;
              dest += bytes;
            }
        }
      else /* HAS MASK, SEMI-OPACITY */
        {
          while (length--)
            {
              src2_alpha = INT_MULT(*m, opacity, tmp);
              new_alpha = src1[alpha] +
                INT_MULT((255 - src1[alpha]), src2_alpha, tmp);
              alphify (src2_alpha, new_alpha);

              if (mode_affect)
                {
                  dest[alpha] = (affect[alpha]) ? new_alpha : src1[alpha];
                }
              else
                {
                  dest[alpha] = (src1[alpha]) ? src1[alpha] :
                    (affect[alpha] ? new_alpha : src1[alpha]);
                }

              m++;
              src1 += bytes;
              src2 += src2_bytes;
              dest += bytes;
            }
        }
    }
  else /* NO MASK */
    {
      while (length --)
        {
          src2_alpha = opacity;
          new_alpha = src1[alpha] +
            INT_MULT((255 - src1[alpha]), src2_alpha, tmp);
          alphify (src2_alpha, new_alpha);

          if (mode_affect)
            dest[alpha] = (affect[alpha]) ? new_alpha : src1[alpha];
          else
            dest[alpha] = (src1[alpha]) ? src1[alpha] : (affect[alpha] ? new_alpha : src1[alpha]);

            src1 += bytes;
            src2 += src2_bytes;
            dest += bytes;
        }
    }
}


void
combine_inten_a_and_inten_a_pixels (const guchar   *src1,
                                    const guchar   *src2,
                                    guchar         *dest,
                                    const guchar   *mask,
                                    guint           opacity,
                                    const gboolean *affect,
                                    gboolean        mode_affect,  /*  how does the combination mode affect alpha?  */
                                    guint           length,
                                    guint           bytes)  /*  4 or 2 depending on RGBA or GRAYA  */
{
  guint b;
  guchar src2_alpha;
  guchar new_alpha;
  gfloat ratio, compl_ratio;
  glong tmp;
  const guint alpha = bytes - 1;

  if (mask)
    {
      const guchar *m = mask;

      if (opacity == OPAQUE_OPACITY) /* HAS MASK, FULL OPACITY */
        {
          const gint* mask_ip;
          gint i,j;

          if (length >= sizeof(int))
            {
              /* HEAD */
              i =  (GPOINTER_TO_INT(m) & (sizeof(int)-1));
              if (i != 0)
                {
                  i = sizeof(int) - i;
                  length -= i;
                  while (i--)
                    {
                      /* GUTS */
                      src2_alpha = INT_MULT(src2[alpha], *m, tmp);
                      new_alpha = src1[alpha] +
                        INT_MULT((255 - src1[alpha]), src2_alpha, tmp);

                      alphify (src2_alpha, new_alpha);

                      if (mode_affect)
                        {
                          dest[alpha] = (affect[alpha]) ? new_alpha : src1[alpha];
                        }
                      else
                        {
                          dest[alpha] = (src1[alpha]) ? src1[alpha] :
                            (affect[alpha] ? new_alpha : src1[alpha]);
                        }

                      m++;
                      src1 += bytes;
                      src2 += bytes;
                      dest += bytes;
                      /* GUTS END */
                    }
                }

              /* BODY */
              mask_ip = (const gint *)m;
              i = length / sizeof(int);
              length %= sizeof(int);
              while (i--)
                {
                  if (*mask_ip)
                    {
                      m = (const guchar*)mask_ip;
                      j = sizeof(int);
                      while (j--)
                        {
                          /* GUTS */
                          src2_alpha = INT_MULT(src2[alpha], *m, tmp);
                          new_alpha = src1[alpha] +
                            INT_MULT((255 - src1[alpha]), src2_alpha, tmp);

                          alphify (src2_alpha, new_alpha);

                          if (mode_affect)
                            {
                              dest[alpha] = (affect[alpha]) ? new_alpha : src1[alpha];
                            }
                          else
                            {
                              dest[alpha] = (src1[alpha]) ? src1[alpha] :
                                (affect[alpha] ? new_alpha : src1[alpha]);
                            }

                          m++;
                          src1 += bytes;
                          src2 += bytes;
                          dest += bytes;
                          /* GUTS END */
                        }
                    }
                  else
                    {
                      j = bytes * sizeof(int);
                      src2 += j;
                      while (j--)
                        {
                          *(dest++) = *(src1++);
                        }
                    }
                  mask_ip++;
                }

              m = (const guchar*)mask_ip;
            }

          /* TAIL */
          while (length--)
            {
              /* GUTS */
              src2_alpha = INT_MULT(src2[alpha], *m, tmp);
              new_alpha = src1[alpha] +
                INT_MULT((255 - src1[alpha]), src2_alpha, tmp);

              alphify (src2_alpha, new_alpha);

              if (mode_affect)
                {
                  dest[alpha] = (affect[alpha]) ? new_alpha : src1[alpha];
                }
              else
                {
                  dest[alpha] = (src1[alpha]) ? src1[alpha] :
                    (affect[alpha] ? new_alpha : src1[alpha]);
                }

              m++;
              src1 += bytes;
              src2 += bytes;
              dest += bytes;
              /* GUTS END */
            }
        }
      else /* HAS MASK, SEMI-OPACITY */
        {
          const gint* mask_ip;
          gint i,j;

          if (length >= sizeof(int))
            {
              /* HEAD */
              i = (GPOINTER_TO_INT(m) & (sizeof(int)-1));
              if (i != 0)
                {
                  i = sizeof(int) - i;
                  length -= i;
                  while (i--)
                    {
                      /* GUTS */
                      src2_alpha = INT_MULT3(src2[alpha], *m, opacity, tmp);
                      new_alpha = src1[alpha] +
                        INT_MULT((255 - src1[alpha]), src2_alpha, tmp);

                      alphify (src2_alpha, new_alpha);

                      if (mode_affect)
                        {
                          dest[alpha] = (affect[alpha]) ? new_alpha : src1[alpha];
                        }
                      else
                        {
                          dest[alpha] = (src1[alpha]) ? src1[alpha] :
                            (affect[alpha] ? new_alpha : src1[alpha]);
                        }

                      m++;
                      src1 += bytes;
                      src2 += bytes;
                      dest += bytes;
                      /* GUTS END */
                    }
                }

              /* BODY */
              mask_ip = (const gint *)m;
              i = length / sizeof(int);
              length %= sizeof(int);
              while (i--)
                {
                  if (*mask_ip)
                    {
                      m = (const guchar*)mask_ip;
                      j = sizeof(int);
                      while (j--)
                        {
                          /* GUTS */
                          src2_alpha = INT_MULT3(src2[alpha], *m, opacity, tmp);
                          new_alpha = src1[alpha] +
                            INT_MULT((255 - src1[alpha]), src2_alpha, tmp);

                          alphify (src2_alpha, new_alpha);

                          if (mode_affect)
                            {
                              dest[alpha] = (affect[alpha]) ? new_alpha : src1[alpha];
                            }
                          else
                            {
                              dest[alpha] = (src1[alpha]) ? src1[alpha] :
                                (affect[alpha] ? new_alpha : src1[alpha]);
                            }

                          m++;
                          src1 += bytes;
                          src2 += bytes;
                          dest += bytes;
                          /* GUTS END */
                        }
                    }
                  else
                    {
                      j = bytes * sizeof(int);
                      src2 += j;
                      while (j--)
                        {
                          *(dest++) = *(src1++);
                        }
                    }
                  mask_ip++;
                }

              m = (const guchar*)mask_ip;
            }

          /* TAIL */
          while (length--)
            {
              /* GUTS */
              src2_alpha = INT_MULT3(src2[alpha], *m, opacity, tmp);
              new_alpha = src1[alpha] +
                INT_MULT((255 - src1[alpha]), src2_alpha, tmp);

              alphify (src2_alpha, new_alpha);

              if (mode_affect)
                {
                  dest[alpha] = (affect[alpha]) ? new_alpha : src1[alpha];
                }
              else
                {
                  dest[alpha] = (src1[alpha]) ? src1[alpha] :
                    (affect[alpha] ? new_alpha : src1[alpha]);
                }

              m++;
              src1 += bytes;
              src2 += bytes;
              dest += bytes;
              /* GUTS END */
            }
        }
    }
  else
    {
      if (opacity == OPAQUE_OPACITY) /* NO MASK, FULL OPACITY */
        {
          while (length --)
            {
              src2_alpha = src2[alpha];
              new_alpha = src1[alpha] +
                INT_MULT((255 - src1[alpha]), src2_alpha, tmp);

              alphify (src2_alpha, new_alpha);

              if (mode_affect)
                {
                  dest[alpha] = (affect[alpha]) ? new_alpha : src1[alpha];
                }
              else
                {
                  dest[alpha] = (src1[alpha]) ? src1[alpha] :
                    (affect[alpha] ? new_alpha : src1[alpha]);
                }

              src1 += bytes;
              src2 += bytes;
              dest += bytes;
            }
        }
      else /* NO MASK, SEMI OPACITY */
        {
          while (length --)
            {
              src2_alpha = INT_MULT(src2[alpha], opacity, tmp);
              new_alpha = src1[alpha] +
                INT_MULT((255 - src1[alpha]), src2_alpha, tmp);

              alphify (src2_alpha, new_alpha);

              if (mode_affect)
                {
                  dest[alpha] = (affect[alpha]) ? new_alpha : src1[alpha];
                }
              else
                {
                  dest[alpha] = (src1[alpha]) ? src1[alpha] :
                    (affect[alpha] ? new_alpha : src1[alpha]);
                }

              src1 += bytes;
              src2 += bytes;
              dest += bytes;
            }
        }
    }
}
#undef alphify

void
combine_inten_a_and_channel_mask_pixels (const guchar *src,
                                         const guchar *channel,
                                         guchar       *dest,
                                         const guchar *col,
                                         guint         opacity,
                                         guint         length,
                                         guint         bytes)
{
  gint   alpha, b;
  guchar channel_alpha;
  guchar new_alpha;
  guchar compl_alpha;
  gint   t, s;

  alpha = bytes - 1;
  while (length --)
    {
      channel_alpha = INT_MULT (255 - *channel, opacity, t);
      if (channel_alpha)
        {
          new_alpha = src[alpha] + INT_MULT ((255 - src[alpha]), channel_alpha, t);

          if (new_alpha != 255)
            channel_alpha = (channel_alpha * 255) / new_alpha;
          compl_alpha = 255 - channel_alpha;

          for (b = 0; b < alpha; b++)
            dest[b] = INT_MULT (col[b], channel_alpha, t) +
              INT_MULT (src[b], compl_alpha, s);
          dest[b] = new_alpha;
        }
      else
        memcpy(dest, src, bytes);

      /*  advance pointers  */
      src+=bytes;
      dest+=bytes;
      channel++;
    }
}


void
combine_inten_a_and_channel_selection_pixels (const guchar *src,
                                              const guchar *channel,
                                              guchar       *dest,
                                              const guchar *col,
                                              guint         opacity,
                                              guint         length,
                                              guint         bytes)
{
  gint   alpha, b;
  guchar channel_alpha;
  guchar new_alpha;
  guchar compl_alpha;
  gint   t, s;

  alpha = bytes - 1;
  while (length --)
    {
      channel_alpha = INT_MULT (*channel, opacity, t);
      if (channel_alpha)
        {
          new_alpha = src[alpha] + INT_MULT ((255 - src[alpha]), channel_alpha, t);

          if (new_alpha != 255)
            channel_alpha = (channel_alpha * 255) / new_alpha;
          compl_alpha = 255 - channel_alpha;

          for (b = 0; b < alpha; b++)
            dest[b] = INT_MULT (col[b], channel_alpha, t) +
              INT_MULT (src[b], compl_alpha, s);
          dest[b] = new_alpha;
        }
      else
        memcpy(dest, src, bytes);

      /*  advance pointers  */
      src+=bytes;
      dest+=bytes;
      channel++;
    }
}


/*  paint "behind" the existing pixel row.
 *  This is similar in appearance to painting on a layer below
 *  the existing pixels.
 */

static inline void
behind_inten_pixels (const guchar   *src1,
                     const guchar   *src2,
                     guchar         *dest,
                     const guchar   *mask,
                     guint           opacity,
                     const gboolean *affect,
                     guint           length,
                     guint           bytes1,
                     guint           bytes2)
{
  /* FIXME: Is this supposed to be different than in the other functions? */
  const guint alpha = bytes1 - 1;
  guint        b;
  guchar        src1_alpha;
  guchar        src2_alpha;
  guchar        new_alpha;
  const guchar *m;
  gfloat        ratio, compl_ratio;
  glong         tmp;

  if (mask)
    m = mask;
  else
    m = &no_mask;

  while (length --)
    {
      src1_alpha = src1[alpha];
      src2_alpha = INT_MULT3(src2[alpha], *m, opacity, tmp);
      new_alpha = src2_alpha +
        INT_MULT((255 - src2_alpha), src1_alpha, tmp);
      if (new_alpha)
        ratio = (float) src1_alpha / new_alpha;
      else
        ratio = 0.0;
      compl_ratio = 1.0 - ratio;

      for (b = 0; b < alpha; b++)
        dest[b] = (affect[b]) ?
          (guchar) (src1[b] * ratio + src2[b] * compl_ratio + EPSILON) :
        src1[b];

      dest[alpha] = (affect[alpha]) ? new_alpha : src1[alpha];

      if (mask)
        m++;

      src1 += bytes1;
      src2 += bytes2;
      dest += bytes1;
    }
}


/*  paint "behind" the existing pixel row (for indexed images).
 *  This is similar in appearance to painting on a layer below
 *  the existing pixels.
 */

static inline void
behind_indexed_pixels (const guchar   *src1,
                       const guchar   *src2,
                       guchar         *dest,
                       const guchar   *mask,
                       guint           opacity,
                       const gboolean *affect,
                       guint           length,
                       guint           bytes1,
                       guint           bytes2)
{
  const guint alpha = bytes1 - 1;
  guint b;
  guchar        src1_alpha;
  guchar        src2_alpha;
  guchar        new_alpha;
  const guchar *m;
  glong         tmp;

  if (mask)
    m = mask;
  else
    m = &no_mask;

  /*  the alpha channel  */

  while (length --)
    {
      src1_alpha = src1[alpha];
      src2_alpha = INT_MULT3(src2[alpha], *m, opacity, tmp);
      new_alpha = (src2_alpha > 127) ? OPAQUE_OPACITY : TRANSPARENT_OPACITY;

      for (b = 0; b < bytes1; b++)
        dest[b] = (affect[b] && new_alpha == OPAQUE_OPACITY && (src1_alpha > 127)) ?
          src2[b] : src1[b];

      if (mask)
        m++;

      src1 += bytes1;
      src2 += bytes2;
      dest += bytes1;
    }
}


/*  replace the contents of one pixel row with the other
 *  The operation is still bounded by mask/opacity constraints
 */

static inline void
replace_inten_pixels (const guchar   *src1,
                      const guchar   *src2,
                      guchar         *dest,
                      const guchar   *mask,
                      guint           opacity,
                      const gboolean *affect,
                      guint           length,
                      guint           bytes1,
                      guint           bytes2)
{
  const guint has_alpha1 = HAS_ALPHA (bytes1);
  const guint has_alpha2 = HAS_ALPHA (bytes2);
  const guint bytes = MIN (bytes1, bytes2);
  guint b;
  gint  tmp;

  if (mask)
    {
      guchar        mask_alpha;
      const guchar *m = mask;

      while (length --)
        {
          mask_alpha = INT_MULT(*m, opacity, tmp);

          for (b = 0; b < bytes; b++)
            dest[b] = (affect[b]) ?
              INT_BLEND(src2[b], src1[b], mask_alpha, tmp) :
              src1[b];

          if (has_alpha1 && !has_alpha2)
            dest[b] = src1[b];

          m++;

          src1 += bytes1;
          src2 += bytes2;
          dest += bytes1;
        }
    }
  else
    {
      const guchar mask_alpha = OPAQUE_OPACITY;

      while (length --)
        {
          for (b = 0; b < bytes; b++)
            dest[b] = (affect[b]) ?
              INT_BLEND(src2[b], src1[b], mask_alpha, tmp) :
              src1[b];

          if (has_alpha1 && !has_alpha2)
            dest[b] = src1[b];

          src1 += bytes1;
          src2 += bytes2;
          dest += bytes1;
        }
    }
}

/*  replace the contents of one pixel row with the other
 *  The operation is still bounded by mask/opacity constraints
 */

static inline void
replace_indexed_pixels (const guchar   *src1,
                        const guchar   *src2,
                        guchar         *dest,
                        const guchar   *mask,
                        guint           opacity,
                        const gboolean *affect,
                        guint           length,
                        guint           bytes1,
                        guint           bytes2)
{
  const guint has_alpha1 = HAS_ALPHA (bytes1);
  const guint has_alpha2 = HAS_ALPHA (bytes2);
  const guint bytes = MIN (bytes1, bytes2);
  const guchar *m;
  guint b;
  guchar        mask_alpha;
  gint          tmp;

  if (mask)
    m = mask;
  else
    m = &no_mask;

  while (length --)
    {
      mask_alpha = INT_MULT(*m, opacity, tmp);

      for (b = 0; b < bytes; b++)
        dest[b] = (affect[b] && mask_alpha) ? src2[b] : src1[b];

      if (has_alpha1 && !has_alpha2)
        dest[b] = src1[b];

      if (mask)
        m++;

      src1 += bytes1;
      src2 += bytes2;
      dest += bytes1;
    }
}

/*  apply source 2 to source 1, but in a non-additive way,
 *  multiplying alpha channels  (works for intensity)
 */

static inline void
erase_inten_pixels (const guchar   *src1,
                    const guchar   *src2,
                    guchar         *dest,
                    const guchar   *mask,
                    guint           opacity,
                    const gboolean *affect,
                    guint           length,
                    guint           bytes)
{
  const guint alpha = bytes - 1;
  guint b;
  guchar     src2_alpha;
  glong      tmp;

  if (mask)
    {
      const guchar *m = mask;

      while (length --)
        {
          for (b = 0; b < alpha; b++)
            dest[b] = src1[b];

          src2_alpha = INT_MULT3(src2[alpha], *m, opacity, tmp);
          dest[alpha] = src1[alpha] - INT_MULT(src1[alpha], src2_alpha, tmp);

          m++;

          src1 += bytes;
          src2 += bytes;
          dest += bytes;
        }
    }
  else
    {
      const guchar *m = &no_mask;

      while (length --)
        {
          for (b = 0; b < alpha; b++)
            dest[b] = src1[b];

          src2_alpha = INT_MULT3(src2[alpha], *m, opacity, tmp);
          dest[alpha] = src1[alpha] - INT_MULT(src1[alpha], src2_alpha, tmp);

          src1 += bytes;
          src2 += bytes;
          dest += bytes;
        }
    }
}


/*  apply source 2 to source 1, but in a non-additive way,
 *  multiplying alpha channels  (works for indexed)
 */

static inline void
erase_indexed_pixels (const guchar   *src1,
                      const guchar   *src2,
                      guchar         *dest,
                      const guchar   *mask,
                      guint           opacity,
                      const gboolean *affect,
                      guint           length,
                      guint           bytes)
{
  const guint alpha = bytes - 1;
  const guchar *m;
  guchar src2_alpha;
  guint b;
  glong tmp;

  if (mask)
    m = mask;
  else
    m = &no_mask;

  while (length --)
    {
      for (b = 0; b < alpha; b++)
        dest[b] = src1[b];

      src2_alpha = INT_MULT3(src2[alpha], *m, opacity, tmp);
      dest[alpha] = (src2_alpha > 127) ? TRANSPARENT_OPACITY : src1[alpha];

      if (mask)
        m++;

      src1 += bytes;
      src2 += bytes;
      dest += bytes;
    }
}

void
anti_erase_inten_pixels (const guchar   *src1,
                         const guchar   *src2,
                         guchar         *dest,
                         const guchar   *mask,
                         guint           opacity,
                         const gboolean *affect,
                         guint           length,
                         guint           bytes)
{
  gint          alpha, b;
  guchar        src2_alpha;
  const guchar *m;
  glong         tmp;

  if (mask)
    m = mask;
  else
    m = &no_mask;

  alpha = bytes - 1;
  while (length --)
    {
      for (b = 0; b < alpha; b++)
        dest[b] = src1[b];

      src2_alpha = INT_MULT3(src2[alpha], *m, opacity, tmp);
      dest[alpha] = src1[alpha] + INT_MULT((255 - src1[alpha]), src2_alpha, tmp);

      if (mask)
        m++;

      src1 += bytes;
      src2 += bytes;
      dest += bytes;
    }
}


void
anti_erase_indexed_pixels (const guchar   *src1,
                           const guchar   *src2,
                           guchar         *dest,
                           const guchar   *mask,
                           guint           opacity,
                           const gboolean *affect,
                           guint           length,
                           guint           bytes)
{
  gint          alpha, b;
  guchar        src2_alpha;
  const guchar *m;
  glong         tmp;

  if (mask)
    m = mask;
  else
    m = &no_mask;

  alpha = bytes - 1;
  while (length --)
    {
      for (b = 0; b < alpha; b++)
        dest[b] = src1[b];

      src2_alpha = INT_MULT3(src2[alpha], *m, opacity, tmp);
      dest[alpha] = (src2_alpha > 127) ? OPAQUE_OPACITY : src1[alpha];

      if (mask)
        m++;

      src1 += bytes;
      src2 += bytes;
      dest += bytes;
    }
}


static void
color_erase_helper (GimpRGB       *src,
                    const GimpRGB *color)
{
  GimpRGB alpha;

  alpha.a = src->a;

  if (color->r < 0.0001)
    alpha.r = src->r;
  else if ( src->r > color->r )
    alpha.r = (src->r - color->r) / (1.0 - color->r);
  else if (src->r < color->r)
    alpha.r = (color->r - src->r) / color->r;
  else alpha.r = 0.0;

  if (color->g < 0.0001)
    alpha.g = src->g;
  else if ( src->g > color->g )
    alpha.g = (src->g - color->g) / (1.0 - color->g);
  else if ( src->g < color->g )
    alpha.g = (color->g - src->g) / (color->g);
  else alpha.g = 0.0;

  if (color->b < 0.0001)
    alpha.b = src->b;
  else if ( src->b > color->b )
    alpha.b = (src->b - color->b) / (1.0 - color->b);
  else if ( src->b < color->b )
    alpha.b = (color->b - src->b) / (color->b);
  else alpha.b = 0.0;

  if ( alpha.r > alpha.g )
    {
      if ( alpha.r > alpha.b )
        {
          src->a = alpha.r;
        }
      else
        {
          src->a = alpha.b;
        }
    }
  else if ( alpha.g > alpha.b )
    {
      src->a = alpha.g;
    }
  else
    {
      src->a = alpha.b;
    }

  src->a = (1.0 - color->a) + (src->a * color->a);

  if (src->a < 0.0001)
    return;

  src->r = (src->r - color->r) / src->a + color->r;
  src->g = (src->g - color->g) / src->a + color->g;
  src->b = (src->b - color->b) / src->a + color->b;

  src->a *= alpha.a;
}


void
color_erase_inten_pixels (const guchar   *src1,
                          const guchar   *src2,
                          guchar         *dest,
                          const guchar   *mask,
                          guint           opacity,
                          const gboolean *affect,
                          guint           length,
                          guint           bytes)
{
  guchar        src2_alpha;
  const guchar *m;
  glong         tmp;
  GimpRGB       bgcolor, color;

  if (mask)
    m = mask;
  else
    m = &no_mask;

  while (length --)
    {
      switch (bytes)
        {
        case 2:
          src2_alpha = INT_MULT3 (src2[1], *m, opacity, tmp);

          gimp_rgba_set_uchar (&color,
                               src1[0], src1[0], src1[0], src1[1]);

          gimp_rgba_set_uchar (&bgcolor,
                               src2[0], src2[0], src2[0], src2_alpha);

          color_erase_helper (&color, &bgcolor);

          gimp_rgba_get_uchar (&color, dest, NULL, NULL, dest + 1);
          break;

        case 4:
          src2_alpha = INT_MULT3 (src2[3], *m, opacity, tmp);

          gimp_rgba_set_uchar (&color,
                               src1[0], src1[1], src1[2], src1[3]);

          gimp_rgba_set_uchar (&bgcolor,
                               src2[0], src2[1], src2[2], src2_alpha);

          color_erase_helper (&color, &bgcolor);

          gimp_rgba_get_uchar (&color, dest, dest + 1, dest + 2, dest + 3);
          break;
        }

      if (mask)
        m++;

      src1 += bytes;
      src2 += bytes;
      dest += bytes;
    }
}


void
extract_from_inten_pixels (guchar       *src,
                           guchar       *dest,
                           const guchar *mask,
                           const guchar *bg,
                           gboolean      cut,
                           guint         length,
                           guint         bytes,
                           gboolean      has_alpha)
{
  gint          b, alpha;
  gint          dest_bytes;
  const guchar *m;
  gint          tmp;

  if (mask)
    m = mask;
  else
    m = &no_mask;

  alpha = (has_alpha) ? bytes - 1 : bytes;
  dest_bytes = (has_alpha) ? bytes : bytes + 1;
  while (length --)
    {
      for (b = 0; b < alpha; b++)
        dest[b] = src[b];

      if (has_alpha)
        {
          dest[alpha] = INT_MULT(*m, src[alpha], tmp);
          if (cut)
            src[alpha] = INT_MULT((255 - *m), src[alpha], tmp);
        }
      else
        {
          dest[alpha] = *m;
          if (cut)
            for (b = 0; b < bytes; b++)
              src[b] = INT_BLEND(bg[b], src[b], *m, tmp);
        }

      if (mask)
        m++;

      src += bytes;
      dest += dest_bytes;
    }
}


void
extract_from_indexed_pixels (guchar       *src,
                             guchar       *dest,
                             const guchar *mask,
                             const guchar *cmap,
                             const guchar *bg,
                             gboolean      cut,
                             guint         length,
                             guint         bytes,
                             gboolean      has_alpha)
{
  gint          b;
  gint          index;
  const guchar *m;
  gint          t;

  if (mask)
    m = mask;
  else
    m = &no_mask;

  while (length --)
    {
      index = src[0] * 3;
      for (b = 0; b < 3; b++)
        dest[b] = cmap[index + b];

      if (has_alpha)
        {
          dest[3] = INT_MULT (*m, src[1], t);
          if (cut)
            src[1] = INT_MULT ((255 - *m), src[1], t);
        }
      else
        {
          dest[3] = *m;
          if (cut)
            src[0] = (*m > 127) ? bg[0] : src[0];
        }

      if (mask)
        m++;

      src += bytes;
      dest += 4;
    }
}


/**************************************************/
/*    REGION FUNCTIONS                            */
/**************************************************/

void
color_region (PixelRegion  *dest,
              const guchar *col)
{
  gint    h;
  guchar *s;
  void   *pr;

  for (pr = pixel_regions_register (1, dest);
       pr != NULL;
       pr = pixel_regions_process (pr))
    {
      h = dest->h;
      s = dest->data;

      if (dest->w * dest->bytes == dest->rowstride)
        {
          /* do it all in one function call if we can
           * this hasn't been tested to see if it is a
           * signifigant speed gain yet
           */
          color_pixels (s, col, dest->w * h, dest->bytes);
        }
      else
        {
          while (h--)
            {
              color_pixels (s, col, dest->w, dest->bytes);
              s += dest->rowstride;
            }
        }
    }
}

void
color_region_mask (PixelRegion  *dest,
                   PixelRegion  *mask,
                   const guchar *col)
{
  gint    h;
  guchar *d;
  guchar *m;
  void   *pr;

  for (pr = pixel_regions_register (2, dest, mask);
       pr != NULL;
       pr = pixel_regions_process (pr))
    {
      h = dest->h;
      d = dest->data;
      m = mask->data;

      if (dest->w * dest->bytes == dest->rowstride &&
          mask->w * mask->bytes == mask->rowstride)
        {
          /* do it all in one function call if we can
           * this hasn't been tested to see if it is a
           * signifigant speed gain yet
           */
          color_pixels_mask (d, m, col, dest->w * h, dest->bytes);
        }
      else
        {
          while (h--)
            {
              color_pixels_mask (d, m, col, dest->w, dest->bytes);
              d += dest->rowstride;
              m += mask->rowstride;
            }
        }
    }
}

void
pattern_region (PixelRegion  *dest,
                PixelRegion  *mask,
                TempBuf      *pattern,
                gint          off_x,
                gint          off_y)
{
  gint    y;
  guchar *d;
  guchar *m = NULL;
  void   *pr;

  for (pr = pixel_regions_register (2, dest, mask);
       pr != NULL;
       pr = pixel_regions_process (pr))
    {
      d = dest->data;

      if (mask)
        m = mask->data;

      for (y = 0; y < dest->h; y++)
        {
          pattern_pixels_mask (d, m, pattern, dest->w, dest->bytes,
                               off_x + dest->x,
                               off_y + dest->y + y);
          d += dest->rowstride;

          if (mask)
            m += mask->rowstride;
        }
    }
}

void
blend_region (PixelRegion *src1,
              PixelRegion *src2,
              PixelRegion *dest,
              guchar       blend)
{
  gint    h;
  guchar *s1, *s2, * d;
  void   *pr;

  for (pr = pixel_regions_register (3, src1, src2, dest);
       pr != NULL;
       pr = pixel_regions_process (pr))
    {
      s1 = src1->data;
      s2 = src2->data;
      d = dest->data;
      h = src1->h;

      while (h --)
        {
          blend_pixels (s1, s2, d, blend, src1->w, src1->bytes);
          s1 += src1->rowstride;
          s2 += src2->rowstride;
          d += dest->rowstride;
        }
    }
}


void
shade_region (PixelRegion *src,
              PixelRegion *dest,
              guchar      *color,
              guchar       blend)
{
  gint    h;
  guchar *s, * d;

  s = src->data;
  d = dest->data;
  h = src->h;

  while (h --)
    {
/*      blend_pixels (s, d, col, blend, src->w, src->bytes);*/
      s += src->rowstride;
      d += dest->rowstride;
    }
}


void
copy_region (PixelRegion *src,
             PixelRegion *dest)
{
  gint    h;
  gint    pixelwidth;
  guchar *s, *d;
  void   *pr;

#ifdef COWSHOW
  fputc ('[',stderr);
#endif
  for (pr = pixel_regions_register (2, src, dest);
       pr != NULL;
       pr = pixel_regions_process (pr))
    {
      if (src->tiles && dest->tiles &&
          src->curtile && dest->curtile &&
          src->offx == 0 && dest->offx == 0 &&
          src->offy == 0 && dest->offy == 0 &&
          src->w  == tile_ewidth (src->curtile)  &&
          dest->w == tile_ewidth (dest->curtile) &&
          src->h  == tile_eheight (src->curtile) &&
          dest->h == tile_eheight (dest->curtile))
        {
#ifdef COWSHOW
          fputc('!',stderr);
#endif
          tile_manager_map_over_tile (dest->tiles, dest->curtile, src->curtile);
        }
      else
        {
#ifdef COWSHOW
          fputc ('.',stderr);
#endif
          pixelwidth = src->w * src->bytes;
          s = src->data;
          d = dest->data;
          h = src->h;

          while (h --)
            {
              memcpy (d, s, pixelwidth);
              s += src->rowstride;
              d += dest->rowstride;
            }
        }
    }

#ifdef COWSHOW
  fputc (']',stderr);
  fputc ('\n',stderr);
#endif
}

void
copy_region_nocow (PixelRegion *src,
                   PixelRegion *dest)
{
  gint    h;
  gint    pixelwidth;
  guchar *s, *d;
  void   *pr;

  for (pr = pixel_regions_register (2, src, dest);
       pr != NULL;
       pr = pixel_regions_process (pr))
    {
      pixelwidth = src->w * src->bytes;
      s = src->data;
      d = dest->data;
      h = src->h;

      while (h --)
        {
          memcpy (d, s, pixelwidth);
          s += src->rowstride;
          d += dest->rowstride;
        }
    }
}


void
add_alpha_region (PixelRegion *src,
                  PixelRegion *dest)
{
  gint    h;
  guchar *s, *d;
  void   *pr;

  for (pr = pixel_regions_register (2, src, dest);
       pr != NULL;
       pr = pixel_regions_process (pr))
    {
      s = src->data;
      d = dest->data;
      h = src->h;

      while (h --)
        {
          add_alpha_pixels (s, d, src->w, src->bytes);
          s += src->rowstride;
          d += dest->rowstride;
        }
    }
}


void
flatten_region (PixelRegion *src,
                PixelRegion *dest,
                guchar      *bg)
{
  gint    h;
  guchar *s, *d;
  void   *pr;

  for (pr = pixel_regions_register (2, src, dest);
       pr != NULL;
       pr = pixel_regions_process (pr))
    {
      s = src->data;
      d = dest->data;
      h = src->h;

      while (h --)
        {
          flatten_pixels (s, d, bg, src->w, src->bytes);
          s += src->rowstride;
          d += dest->rowstride;
        }
    }
}


void
extract_alpha_region (PixelRegion *src,
                      PixelRegion *mask,
                      PixelRegion *dest)
{
  gint h;
  guchar * s, * m, * d;
  void * pr;

  for (pr = pixel_regions_register (3, src, mask, dest); pr != NULL; pr = pixel_regions_process (pr))
    {
      s = src->data;
      d = dest->data;
      if (mask)
        m = mask->data;
      else
        m = NULL;

      h = src->h;
      while (h --)
        {
          extract_alpha_pixels (s, m, d, src->w, src->bytes);
          s += src->rowstride;
          d += dest->rowstride;
          if (mask)
            m += mask->rowstride;
        }
    }
}


void
extract_from_region (PixelRegion *src,
                     PixelRegion *dest,
                     PixelRegion *mask,
                     guchar      *cmap,
                     guchar      *bg,
                     gint         type,
                     gboolean     has_alpha,
                     gboolean     cut)
{
  gint    h;
  guchar *s, *d, *m;
  void   *pr;

  for (pr = pixel_regions_register (3, src, dest, mask);
       pr != NULL;
       pr = pixel_regions_process (pr))
    {
      s = src->data;
      d = dest->data;
      m = (mask) ? mask->data : NULL;
      h = src->h;

      while (h --)
        {
          switch (type)
            {
            case 0:  /*  RGB      */
            case 1:  /*  GRAY     */
              extract_from_inten_pixels (s, d, m, bg, cut, src->w,
                                         src->bytes, has_alpha);
              break;
            case 2:  /*  INDEXED  */
              extract_from_indexed_pixels (s, d, m, cmap, bg, cut, src->w,
                                           src->bytes, has_alpha);
              break;
            }

          s += src->rowstride;
          d += dest->rowstride;
          if (mask)
            m += mask->rowstride;
        }
    }
}


void
convolve_region (PixelRegion         *srcR,
                 PixelRegion         *destR,
                 gfloat              *matrix,
                 gint                 size,
                 gdouble              divisor,
                 GimpConvolutionType  mode,
                 gboolean             alpha_weighting)
{
  /*  Convolve the src image using the convolution matrix, writing to dest  */
  /*  Convolve is not tile-enabled--use accordingly  */
  guchar  *src, *s_row, *s;
  guchar  *dest, *d;
  gfloat  *m;
  gdouble  total [4];
  gint     b, bytes;
  gint     alpha, a_byte;
  gint     length;
  gint     wraparound;
  gint     margin;      /*  margin imposed by size of conv. matrix  */
  gint     i, j;
  gint     x, y;
  gint     offset;
  gdouble  matrixsum = 0.0;
  gdouble  weighted_divisor, mult_alpha;

  /*  If the mode is NEGATIVE_CONVOL, the offset should be 128  */
  if (mode == GIMP_NEGATIVE_CONVOL)
    {
      offset = 128;
      mode = GIMP_NORMAL_CONVOL;
    }
  else
    offset = 0;

  /*  check for the boundary cases  */
  if (srcR->w < (size - 1) || srcR->h < (size - 1))
    return;

  /*  Initialize some values  */
  bytes = srcR->bytes;
  a_byte = bytes - 1;
  length = bytes * srcR->w;
  margin = size / 2;
  src = srcR->data;
  dest = destR->data;

  if (alpha_weighting)
    {
      m = matrix;
      i = size;
      while (i --)
        {
          j = size;
          while (j --)
            matrixsum += *m++;
        }
      if (matrixsum == 0.0)
        matrixsum = 1.0;
    }

  /*  calculate the source wraparound value  */
  wraparound = srcR->rowstride - size * bytes;

  /* copy the first (size / 2) scanlines of the src image... */
  for (i = 0; i < margin; i++)
    {
      memcpy (dest, src, length);
      src += srcR->rowstride;
      dest += destR->rowstride;
    }

  src = srcR->data;

  for (y = margin; y < srcR->h - margin; y++)
    {
      s_row = src;
      s = s_row + srcR->rowstride*margin;
      d = dest;

      /* handle the first margin pixels... */
      b = bytes * margin;
      while (b --)
        *d++ = *s++;

      /* now, handle the center pixels */
      x = srcR->w - margin*2;

      if (alpha_weighting)
        while (x--)
          {
            s = s_row;

            m = matrix;
            total [0] = total [1] = total [2] = total [3] = 0.0;
            weighted_divisor = 0.0;

            i = size;
            while (i --)
              {
                j = size;
                while (j --)
                  {
                    alpha = s [a_byte];

                    if (alpha && *m)
                      {
                        mult_alpha = *m * alpha;
                        weighted_divisor += mult_alpha;

                        for (b = 0; b < a_byte; b++)
                          total [b] += mult_alpha * *s++;
                        total [a_byte] += *m * *s++;
                      }
                    else
                      s += bytes;

                    m ++;
                  }

                s += wraparound;
              }

            if (weighted_divisor == 0.0)
              weighted_divisor = 1.0;

            for (b = 0; b < bytes; b++)
              {
                total [b] /= divisor;
                if (b != a_byte)
                  total [b] = total [b] * matrixsum / weighted_divisor;
                total [b] += offset;

                if (total [b] < 0.0 && mode != GIMP_NORMAL_CONVOL)
                  total [b] = - total [b];

                if (total [b] < 0)
                  *d++ = 0;
                else
                  *d++ = (total [b] > 255.0) ? 255 : (guchar) total [b];
              }

            s_row += bytes;
          }
      else
        while (x--)
          {
            s = s_row;

            m = matrix;
            total [0] = total [1] = total [2] = total [3] = 0.0;

            i = size;
            while (i --)
              {
                j = size;
                while (j --)
                  {
                    if (*m)
                      for (b = 0; b < bytes; b++)
                        total [b] += *m * *s++;
                    else
                      s += bytes;
                    m ++;
                  }

                s += wraparound;
              }

            for (b = 0; b < bytes; b++)
              {
                total [b] = total [b] / divisor + offset;

                if (total [b] < 0.0 && mode != GIMP_NORMAL_CONVOL)
                  total [b] = - total [b];

                if (total [b] < 0.0)
                  *d++ = 0.0;
                else
                  *d++ = (total [b] > 255.0) ? 255 : (guchar) total [b];
              }

            s_row += bytes;
          }

      /* handle the last pixel... */
      s = s_row + (srcR->rowstride + bytes) * margin;
      b = bytes * margin;
      while (b --)
        *d++ = *s++;

      /* set the memory pointers */
      src += srcR->rowstride;
      dest += destR->rowstride;
    }

  src += srcR->rowstride*margin;

  /* copy the last (margin) scanlines of the src image... */
  for (i = 0; i < margin; i++)
    {
      memcpy (dest, src, length);
      src += srcR->rowstride;
      dest += destR->rowstride;
    }
}

/* Convert from separated alpha to premultiplied alpha. Only works on
   non-tiled regions! */
void
multiply_alpha_region (PixelRegion *srcR)
{
  guchar  *src, *s;
  gint     x, y;
  gint     width, height;
  gint     b, bytes;
  gdouble  alpha_val;

  width = srcR->w;
  height = srcR->h;
  bytes = srcR->bytes;

  src = srcR->data;

  for (y = 0; y < height; y++)
    {
      s = src;
      for (x = 0; x < width; x++)
        {
          alpha_val = s[bytes - 1] * (1.0 / 255.0);
          for (b = 0; b < bytes - 1; b++)
            s[b] = 0.5 + s[b] * alpha_val;
          s += bytes;
        }
      src += srcR->rowstride;
    }
}

/* Convert from premultiplied alpha to separated alpha. Only works on
   non-tiled regions! */
void
separate_alpha_region (PixelRegion *srcR)
{
  guchar  *src, *s;
  gint     x, y;
  gint     width, height;
  gint     b, bytes;
  gdouble  alpha_recip;
  gint     new_val;

  width = srcR->w;
  height = srcR->h;
  bytes = srcR->bytes;

  src = srcR->data;

  for (y = 0; y < height; y++)
    {
      s = src;
      for (x = 0; x < width; x++)
        {
          /* predicate is equivalent to:
             (((s[bytes - 1] - 1) & 255) + 2) & 256
             */
          if (s[bytes - 1] != 0 && s[bytes - 1] != 255)
            {
              alpha_recip = 255.0 / s[bytes - 1];
              for (b = 0; b < bytes - 1; b++)
                {
                  new_val = 0.5 + s[b] * alpha_recip;
                  new_val = MIN (new_val, 255);
                  s[b] = new_val;
                }
            }
          s += bytes;
        }
      src += srcR->rowstride;
    }
}

void
gaussian_blur_region (PixelRegion *srcR,
                      gdouble      radius_x,
                      gdouble      radius_y)
{
  gdouble std_dev;
  glong   width, height;
  guint   bytes;
  guchar *src, *sp;
  guchar *dest, *dp;
  guchar *data;
  gint   *buf, *b;
  gint    pixels;
  gint    total;
  gint    i, row, col;
  gint    start, end;
  gint   *curve;
  gint   *sum;
  gint    val;
  gint    length;
  gint    alpha;
  gint    initial_p, initial_m;

  if (radius_x == 0.0 && radius_y == 0.0) return;    /* zero blur is a no-op */

  /*  allocate the result buffer  */
  length = MAX (srcR->w, srcR->h) * srcR->bytes;
  data = g_new (guchar, length * 2);
  src = data;
  dest = data + length;

  width = srcR->w;
  height = srcR->h;
  bytes = srcR->bytes;
  alpha = bytes - 1;

  buf = g_new (gint, MAX (width, height) * 2);

  if (radius_y != 0.0)
    {
      std_dev = sqrt (-(radius_y * radius_y) / (2 * log (1.0 / 255.0)));
      curve = make_curve (std_dev, &length);
      sum = g_new (gint, 2 * length + 1);
      sum[0] = 0;

      for (i = 1; i <= length*2; i++)
        sum[i] = curve[i - length - 1] + sum[i - 1];
      sum += length;

      total = sum[length] - sum[-length];

      for (col = 0; col < width; col++)
        {
          pixel_region_get_col (srcR, col + srcR->x, srcR->y, height, src, 1);
          sp = src + alpha;

          initial_p = sp[0];
          initial_m = sp[(height - 1) * bytes];

          /*  Determine a run-length encoded version of the column  */
          run_length_encode (sp, buf, height, bytes);

          for (row = 0; row < height; row++)
            {
              start = (row < length) ? -row : -length;
              end = (height <= (row + length)) ? (height - row - 1) : length;

              val = 0;
              i = start;
              b = buf + (row + i) * 2;

              if (start != -length)
                val += initial_p * (sum[start] - sum[-length]);

              while (i < end)
                {
                  pixels = b[0];
                  i += pixels;
                  if (i > end)
                    i = end;
                  val += b[1] * (sum[i] - sum[start]);
                  b += (pixels * 2);
                  start = i;
                }

              if (end != length)
                val += initial_m * (sum[length] - sum[end]);

              sp[row * bytes] = val / total;
            }

          pixel_region_set_col (srcR, col + srcR->x, srcR->y, height, src);
        }

      g_free (sum - length);
      g_free (curve - length);
    }

  if (radius_x != 0.0)
    {
      std_dev = sqrt (-(radius_x * radius_x) / (2 * log (1.0 / 255.0)));
      curve = make_curve (std_dev, &length);
      sum = g_new (gint, 2 * length + 1);
      sum[0] = 0;

      for (i = 1; i <= length * 2; i++)
        sum[i] = curve[i - length - 1] + sum[i - 1];
      sum += length;

      total = sum[length] - sum[-length];

      for (row = 0; row < height; row++)
        {
          pixel_region_get_row (srcR, srcR->x, row + srcR->y, width, src, 1);
          sp = src + alpha;
          dp = dest + alpha;

          initial_p = sp[0];
          initial_m = sp[(width - 1) * bytes];

          /*  Determine a run-length encoded version of the row  */
          run_length_encode (sp, buf, width, bytes);

          for (col = 0; col < width; col++)
            {
              start = (col < length) ? -col : -length;
              end = (width <= (col + length)) ? (width - col - 1) : length;

              val = 0;
              i = start;
              b = buf + (col + i) * 2;

              if (start != -length)
                val += initial_p * (sum[start] - sum[-length]);

              while (i < end)
                {
                  pixels = b[0];
                  i += pixels;
                  if (i > end)
                    i = end;
                  val += b[1] * (sum[i] - sum[start]);
                  b += (pixels * 2);
                  start = i;
                }

              if (end != length)
                val += initial_m * (sum[length] - sum[end]);

              val = val / total;

              dp[col * bytes] = val;
            }

          pixel_region_set_row (srcR, srcR->x, row + srcR->y, width, dest);
        }

      g_free (sum - length);
      g_free (curve - length);
    }

  g_free (data);
  g_free (buf);
}


/* non-interpolating scale_region.  [adam]
 */
static void
scale_region_no_resample (PixelRegion *srcPR,
                          PixelRegion *destPR)
{
  gint   *x_src_offsets;
  gint   *y_src_offsets;
  guchar *src;
  guchar *dest;
  gint    width, height, orig_width, orig_height;
  gint    last_src_y;
  gint    row_bytes;
  gint    x, y, b;
  gchar   bytes;

  orig_width = srcPR->w;
  orig_height = srcPR->h;

  width = destPR->w;
  height = destPR->h;

  bytes = srcPR->bytes;

  /*  the data pointers...  */
  x_src_offsets = g_new (gint, width * bytes);
  y_src_offsets = g_new (gint, height);
  src  = g_new (guchar, orig_width * bytes);
  dest = g_new (guchar, width * bytes);

  /*  pre-calc the scale tables  */
  for (b = 0; b < bytes; b++)
    for (x = 0; x < width; x++)
      x_src_offsets [b + x * bytes] =
        b + bytes * ((x * orig_width + orig_width / 2) / width);

  for (y = 0; y < height; y++)
    y_src_offsets [y] = (y * orig_height + orig_height / 2) / height;

  /*  do the scaling  */
  row_bytes = width * bytes;
  last_src_y = -1;
  for (y = 0; y < height; y++)
    {
      /* if the source of this line was the same as the source
       *  of the last line, there's no point in re-rescaling.
       */
      if (y_src_offsets[y] != last_src_y)
        {
          pixel_region_get_row (srcPR, 0, y_src_offsets[y], orig_width, src, 1);
          for (x = 0; x < row_bytes ; x++)
            {
              dest[x] = src[x_src_offsets[x]];
            }
          last_src_y = y_src_offsets[y];
        }

      pixel_region_set_row (destPR, 0, y, width, dest);
    }

  g_free (x_src_offsets);
  g_free (y_src_offsets);
  g_free (src);
  g_free (dest);
}


static void
get_premultiplied_double_row (PixelRegion *srcPR,
                              gint         x,
                              gint         y,
                              gint         w,
                              gdouble     *row,
                              guchar      *tmp_src,
                              gint         n)
{
  gint b;
  gint bytes = srcPR->bytes;

  pixel_region_get_row (srcPR, x, y, w, tmp_src, n);

  if (pixel_region_has_alpha (srcPR))
    {
      /* premultiply the alpha into the double array */
      gdouble *irow  = row;
      gint     alpha = bytes - 1;
      gdouble  mod_alpha;

      for (x = 0; x < w; x++)
        {
          mod_alpha = tmp_src[alpha] / 255.0;
          for (b = 0; b < alpha; b++)
            irow[b] = mod_alpha * tmp_src[b];
          irow[b] = tmp_src[alpha];
          irow += bytes;
          tmp_src += bytes;
        }
    }
  else /* no alpha */
    {
      for (x = 0; x < w * bytes; x++)
        row[x] = tmp_src[x];
    }

  /* set the off edge pixels to their nearest neighbor */
  for (b = 0; b < 2 * bytes; b++)
    row[b - 2 * bytes] = row[b % bytes];
  for (b = 0; b < bytes * 2; b++)
    row[b + w * bytes] = row[(w - 1) * bytes + b % bytes];
}


static void
expand_line (gdouble               *dest,
             gdouble               *src,
             gint                   bytes,
             gint                   old_width,
             gint                   width,
             GimpInterpolationType  interp)
{
  gdouble  ratio;
  gint     x,b;
  gint     src_col;
  gdouble  frac;
  gdouble *s;

  ratio = old_width / (gdouble) width;

  /* we can overflow src's boundaries, so we expect our caller to have
     allocated extra space for us to do so safely (see scale_region ()) */

  /* this could be optimized much more by precalculating the coefficients for
     each x */
  switch(interp)
    {
    case GIMP_INTERPOLATION_CUBIC:
      for (x = 0; x < width; x++)
        {
          src_col = ((int) (x * ratio + 2.0 - 0.5)) - 2;
          /* +2, -2 is there because (int) rounds towards 0 and we need
             to round down */
          frac = (x * ratio - 0.5) - src_col;
          s = &src[src_col * bytes];
          for (b = 0; b < bytes; b++)
            dest[b] = cubic (frac, s[b - bytes], s[b], s[b + bytes],
                             s[b + bytes * 2]);
          dest += bytes;
        }

      break;

    case GIMP_INTERPOLATION_LINEAR:
      for (x = 0; x < width; x++)
        {
          src_col = ((int) (x * ratio + 2.0 - 0.5)) - 2;
          /* +2, -2 is there because (int) rounds towards 0 and we need
             to round down */
          frac = (x * ratio - 0.5) - src_col;
          s = &src[src_col * bytes];
          for (b = 0; b < bytes; b++)
            dest[b] = ((s[b + bytes] - s[b]) * frac + s[b]);
          dest += bytes;
        }
      break;

    case GIMP_INTERPOLATION_NONE:
      g_assert_not_reached ();
      break;
    }
}


static void
shrink_line (gdouble               *dest,
             gdouble               *src,
             gint                   bytes,
             gint                   old_width,
             gint                   width,
             GimpInterpolationType  interp)
{
  gint          x;
  gint          b;
  gdouble      *srcp;
  gdouble      *destp;
  gdouble       accum[4];
  gdouble       slice;
  const gdouble avg_ratio = (gdouble) width / old_width;
  const gdouble inv_width = 1.0 / width;
  gint          slicepos;      /* slice position relative to width */

#if 0
  g_printerr ("shrink_line bytes=%d old_width=%d width=%d interp=%d "
              "avg_ratio=%f\n",
              bytes, old_width, width, interp, avg_ratio);
#endif

  g_return_if_fail (bytes <= 4);

  /* This algorithm calculates the weighted average of pixel data that
     each output pixel must receive, taking into account that it always
     scales down, i.e. there's always more than one input pixel per each
     output pixel.  */

  srcp = src;
  destp = dest;

  slicepos = 0;

  /* Initialize accum to the first pixel slice.  As there is no partial
     pixel at start, that value is 0.  The source data is interleaved, so
     we maintain BYTES accumulators at the same time to deal with that
     many channels simultaneously.  */
  for (b = 0; b < bytes; b++)
    accum[b] = 0.0;

  for (x = 0; x < width; x++)
    {
      /* Accumulate whole pixels.  */
      do
        {
          for (b = 0; b < bytes; b++)
            accum[b] += *srcp++;

          slicepos += width;
        }
      while (slicepos < old_width);
      slicepos -= old_width;

      if (! (slicepos < width))
        g_warning ("Assertion (slicepos < width) failed. Please report.");

      if (slicepos == 0)
        {
          /* Simplest case: we have reached a whole pixel boundary.  Store
             the average value per channel and reset the accumulators for
             the next round.

             The main reason to treat this case separately is to avoid an
             access to out-of-bounds memory for the first pixel.  */
          for (b = 0; b < bytes; b++)
            {
              *destp++ = accum[b] * avg_ratio;
              accum[b] = 0.0;
            }
        }
      else
        {
          for (b = 0; b < bytes; b++)
            {
              /* We have accumulated a whole pixel per channel where just a
                 slice of it was needed.  Subtract now the previous pixel's
                 extra slice.  */
              slice = srcp[- bytes + b] * slicepos * inv_width;
              *destp++ = (accum[b] - slice) * avg_ratio;

              /* That slice is the initial value for the next round.  */
              accum[b] = slice;
            }
        }
    }

  /* Sanity check: srcp should point to the next-to-last position, and
     slicepos should be zero.  */
  if (! (srcp - src == old_width * bytes && slicepos == 0))
    g_warning ("Assertion (srcp - src == old_width * bytes && slicepos == 0)"
               " failed. Please report.");
}

static inline void
rotate_pointers (guchar  **p,
                 guint32   n)
{
  guint32  i;
  guchar  *tmp;

  tmp = p[0];
  for (i = 0; i < n-1; i++)
    {
      p[i] = p[i+1];
    }
  p[i] = tmp;
}

static void
get_scaled_row (gdouble              **src,
                gint                   y,
                gint                   new_width,
                PixelRegion           *srcPR,
                gdouble               *row,
                guchar                *src_tmp,
                GimpInterpolationType  interpolation_type)
{
  /* get the necesary lines from the source image, scale them,
     and put them into src[] */
  rotate_pointers ((gpointer) src, 4);
  if (y < 0)
    y = 0;
  if (y < srcPR->h)
    {
      get_premultiplied_double_row (srcPR, 0, y, srcPR->w,
                                    row, src_tmp, 1);
      if (new_width > srcPR->w)
        expand_line(src[3], row, srcPR->bytes,
                    srcPR->w, new_width, interpolation_type);
      else if (srcPR->w > new_width)
        shrink_line(src[3], row, srcPR->bytes,
                    srcPR->w, new_width, interpolation_type);
      else /* no scailing needed */
        memcpy(src[3], row, sizeof (gdouble) * new_width * srcPR->bytes);
    }
  else
    memcpy(src[3], src[2], sizeof (gdouble) * new_width * srcPR->bytes);
}

void
scale_region (PixelRegion           *srcPR,
              PixelRegion           *destPR,
              GimpInterpolationType  interpolation,
              GimpProgressFunc       progress_callback,
              gpointer               progress_data)
{
  gdouble *src[4];
  guchar  *src_tmp;
  guchar  *dest;
  gdouble *row, *accum;
  gint     bytes, b;
  gint     width, height;
  gint     orig_width, orig_height;
  gdouble  y_rat;
  gint     i;
  gint     old_y = -4;
  gint     new_y;
  gint     x, y;

  if (interpolation == GIMP_INTERPOLATION_NONE)
    {
      scale_region_no_resample (srcPR, destPR);
      return;
    }

  orig_width = srcPR->w;
  orig_height = srcPR->h;

  width = destPR->w;
  height = destPR->h;

#if 0
  g_printerr ("scale_region: (%d x %d) -> (%d x %d)\n",
              orig_width, orig_height, width, height);
#endif

  /*  find the ratios of old y to new y  */
  y_rat = (gdouble) orig_height / (gdouble) height;

  bytes = destPR->bytes;

  /*  the data pointers...  */
  for (i = 0; i < 4; i++)
    src[i] = g_new (gdouble, width * bytes);

  dest = g_new (guchar, width * bytes);

  src_tmp = g_new (guchar, orig_width * bytes);

  /* offset the row pointer by 2*bytes so the range of the array
     is [-2*bytes] to [(orig_width + 2)*bytes] */
  row = g_new (gdouble, (orig_width + 2 * 2) * bytes);
  row += bytes * 2;

  accum = g_new (gdouble, width * bytes);

  /*  Scale the selected region  */

  for (y = 0; y < height; y++)
    {
      if (progress_callback && !(y & 0xf))
        (* progress_callback) (0, height, y, progress_data);

      if (height < orig_height)
        {
          gint          max;
          gdouble       frac;
          const gdouble inv_ratio = 1.0 / y_rat;

          if (y == 0) /* load the first row if this is the first time through */
            get_scaled_row (&src[0], 0, width, srcPR, row,
                            src_tmp,
                            interpolation);
          new_y = (int) (y * y_rat);
          frac = 1.0 - (y * y_rat - new_y);
          for (x = 0; x < width * bytes; x++)
            accum[x] = src[3][x] * frac;

          max = (int) ((y + 1) * y_rat) - new_y - 1;

          get_scaled_row (&src[0], ++new_y, width, srcPR, row,
                          src_tmp,
                          interpolation);

          while (max > 0)
            {
              for (x = 0; x < width * bytes; x++)
                accum[x] += src[3][x];
              get_scaled_row (&src[0], ++new_y, width, srcPR, row,
                              src_tmp,
                              interpolation);
              max--;
            }
          frac = (y + 1) * y_rat - ((int) ((y + 1) * y_rat));
          for (x = 0; x < width * bytes; x++)
            {
              accum[x] += frac * src[3][x];
              accum[x] *= inv_ratio;
            }
        }
      else if (height > orig_height)
        {
          new_y = floor (y * y_rat - 0.5);

          while (old_y <= new_y)
            {
              /* get the necesary lines from the source image, scale them,
                 and put them into src[] */
              get_scaled_row (&src[0], old_y + 2, width, srcPR, row,
                              src_tmp,
                              interpolation);
              old_y++;
            }

          switch (interpolation)
            {
            case GIMP_INTERPOLATION_CUBIC:
              {
                gdouble p0, p1, p2, p3;
                gdouble dy = (y * y_rat - 0.5) - new_y;

                p0 = cubic (dy, 1, 0, 0, 0);
                p1 = cubic (dy, 0, 1, 0, 0);
                p2 = cubic (dy, 0, 0, 1, 0);
                p3 = cubic (dy, 0, 0, 0, 1);
                for (x = 0; x < width * bytes; x++)
                  accum[x] = (p0 * src[0][x] + p1 * src[1][x] +
                              p2 * src[2][x] + p3 * src[3][x]);
              }

              break;

            case GIMP_INTERPOLATION_LINEAR:
              {
                gdouble idy = (y * y_rat - 0.5) - new_y;
                gdouble dy = 1.0 - idy;

                for (x = 0; x < width * bytes; x++)
                  accum[x] = dy * src[1][x] + idy * src[2][x];
              }

              break;

            case GIMP_INTERPOLATION_NONE:
              g_assert_not_reached ();
              break;
            }
        }
      else /* height == orig_height */
        {
          get_scaled_row (&src[0], y, width, srcPR, row,
                          src_tmp,
                          interpolation);
          memcpy (accum, src[3], sizeof (gdouble) * width * bytes);
        }

      if (pixel_region_has_alpha (srcPR))
        {
          /* unmultiply the alpha */
          gdouble  inv_alpha;
          gdouble *p = accum;
          gint     alpha = bytes - 1;
          gint     result;
          guchar  *d = dest;

          for (x = 0; x < width; x++)
            {
              if (p[alpha] > 0.001)
                {
                  inv_alpha = 255.0 / p[alpha];
                  for (b = 0; b < alpha; b++)
                    {
                      result = RINT (inv_alpha * p[b]);
                      if (result < 0)
                        d[b] = 0;
                      else if (result > 255)
                        d[b] = 255;
                      else
                        d[b] = result;
                    }
                  result = RINT (p[alpha]);
                  if (result > 255)
                    d[alpha] = 255;
                  else
                    d[alpha] = result;
                }
              else /* alpha <= 0 */
                for (b = 0; b <= alpha; b++)
                  d[b] = 0;

              d += bytes;
              p += bytes;
            }
        }
      else
        {
          gint w = width * bytes;

          for (x = 0; x < w; x++)
            {
              if (accum[x] < 0.0)
                dest[x] = 0;
              else if (accum[x] > 255.0)
                dest[x] = 255;
              else
                dest[x] = RINT (accum[x]);
            }
        }
      pixel_region_set_row (destPR, 0, y, width, dest);
    }

  /*  free up temporary arrays  */
  g_free (accum);
  for (i = 0; i < 4; i++)
    g_free (src[i]);
  g_free (src_tmp);
  g_free (dest);

  row -= 2 * bytes;
  g_free (row);
}

void
subsample_region (PixelRegion *srcPR,
                  PixelRegion *destPR,
                  gint         subsample)
{
  guchar  *src,  *s;
  guchar  *dest, *d;
  gdouble *row,  *r;
  gint destwidth;
  gint src_row, src_col;
  gint bytes, b;
  gint width, height;
  gint orig_width, orig_height;
  gdouble x_rat, y_rat;
  gdouble x_cum, y_cum;
  gdouble x_last, y_last;
  gdouble * x_frac, y_frac, tot_frac;
  gint i, j;
  gint frac;
  gint advance_dest;

  orig_width = srcPR->w / subsample;
  orig_height = srcPR->h / subsample;
  width = destPR->w;
  height = destPR->h;

#if 0
  g_printerr ("subsample_region: (%d x %d) -> (%d x %d)\n",
              orig_width, orig_height, width, height);
#endif

  /*  Some calculations...  */
  bytes = destPR->bytes;
  destwidth = destPR->rowstride;

  /*  the data pointers...  */
  src  = g_new (guchar, orig_width * bytes);
  dest = destPR->data;

  /*  find the ratios of old x to new x and old y to new y  */
  x_rat = (gdouble) orig_width / (gdouble) width;
  y_rat = (gdouble) orig_height / (gdouble) height;

  /*  allocate an array to help with the calculations  */
  row    = g_new (gdouble, width * bytes);
  x_frac = g_new (gdouble, width + orig_width);

  /*  initialize the pre-calculated pixel fraction array  */
  src_col = 0;
  x_cum = (gdouble) src_col;
  x_last = x_cum;

  for (i = 0; i < width + orig_width; i++)
    {
      if (x_cum + x_rat <= (src_col + 1 + EPSILON))
        {
          x_cum += x_rat;
          x_frac[i] = x_cum - x_last;
        }
      else
        {
          src_col ++;
          x_frac[i] = src_col - x_last;
        }
      x_last += x_frac[i];
    }

  /*  clear the "row" array  */
  memset (row, 0, sizeof (gdouble) * width * bytes);

  /*  counters...  */
  src_row = 0;
  y_cum = (gdouble) src_row;
  y_last = y_cum;

  pixel_region_get_row (srcPR, 0, src_row * subsample, orig_width * subsample, src, subsample);

  /*  Scale the selected region  */
  for (i = 0; i < height; )
    {
      src_col = 0;
      x_cum = (gdouble) src_col;

      /* determine the fraction of the src pixel we are using for y */
      if (y_cum + y_rat <= (src_row + 1 + EPSILON))
        {
          y_cum += y_rat;
          y_frac = y_cum - y_last;
          advance_dest = TRUE;
        }
      else
        {
          src_row ++;
          y_frac = src_row - y_last;
          advance_dest = FALSE;
        }

      y_last += y_frac;

      s = src;
      r = row;

      frac = 0;
      j = width;

      while (j)
        {
          tot_frac = x_frac[frac++] * y_frac;

          for (b = 0; b < bytes; b++)
            r[b] += s[b] * tot_frac;

          /*  increment the destination  */
          if (x_cum + x_rat <= (src_col + 1 + EPSILON))
            {
              r += bytes;
              x_cum += x_rat;
              j--;
            }

          /* increment the source */
          else
            {
              s += bytes;
              src_col++;
            }
        }

      if (advance_dest)
        {
          tot_frac = 1.0 / (x_rat * y_rat);

          /*  copy "row" to "dest"  */
          d = dest;
          r = row;

          j = width;
          while (j--)
            {
              b = bytes;
              while (b--)
                *d++ = (guchar) (*r++ * tot_frac + 0.5);
            }

          dest += destwidth;

          /*  clear the "row" array  */
          memset (row, 0, sizeof (gdouble) * destwidth);

          i++;
        }
      else
        pixel_region_get_row (srcPR, 0, src_row * subsample, orig_width * subsample, src, subsample);
    }

  /*  free up temporary arrays  */
  g_free (row);
  g_free (x_frac);
  g_free (src);
}


gfloat
shapeburst_region (PixelRegion      *srcPR,
                   PixelRegion      *distPR,
                   GimpProgressFunc  progress_callback,
                   gpointer          progress_data)
{
  Tile   *tile;
  guchar *tile_data;
  gfloat  max_iterations = 0.0;
  gfloat *distp_cur;
  gfloat *distp_prev;
  gfloat *memory;
  gfloat *tmp;
  gfloat  min_prev;
  gfloat  float_tmp;
  gint    min;
  gint    min_left;
  gint    length;
  gint    i, j, k;
  gint    fraction;
  gint    prev_frac;
  gint    x, y;
  gint    end;
  gint    boundary;
  gint    inc;
  gint    src          = 0;
  gint    max_progress = srcPR->w * srcPR->h;
  gint    progress     = 0;

  length = distPR->w + 1;
  memory = g_new (gfloat, length * 2);

  distp_prev = memory;
  for (i = 0; i < length; i++)
    distp_prev[i] = 0.0;

  distp_prev += 1;
  distp_cur = distp_prev + length;

  for (i = 0; i < srcPR->h; i++)
    {
      /*  set the current dist row to 0's  */
      memset (distp_cur - 1, 0, sizeof (gfloat) * (length - 1));

      for (j = 0; j < srcPR->w; j++)
        {
          min_prev = MIN (distp_cur[j-1], distp_prev[j]);
          min_left = MIN ((srcPR->w - j - 1), (srcPR->h - i - 1));
          min = (gint) MIN (min_left, min_prev);
          fraction = 255;

          /*  This might need to be changed to 0
              instead of k = (min) ? (min - 1) : 0  */

          for (k = (min) ? (min - 1) : 0; k <= min; k++)
            {
              x = j;
              y = i + k;
              end = y - k;

              while (y >= end)
                {
                  gint width;

                  tile = tile_manager_get_tile (srcPR->tiles,
                                                x, y, TRUE, FALSE);

                  tile_data = tile_data_pointer (tile,
                                                 x % TILE_WIDTH,
                                                 y % TILE_HEIGHT);
                  width = tile_ewidth (tile);

                  boundary = MIN (y % TILE_HEIGHT,
                                  width - (x % TILE_WIDTH) - 1);
                  boundary = MIN (boundary, y - end) + 1;

                  inc = 1 - width;

                  while (boundary--)
                    {
                      src = *tile_data;
                      if (src == 0)
                        {
                          min = k;
                          y = -1;
                          break;
                        }
                      if (src < fraction)
                        fraction = src;

                      x++;
                      y--;
                      tile_data += inc;
                    }

                  tile_release (tile, FALSE);
                }
            }

          if (src != 0)
            {
              /*  If min_left != min_prev use the previous fraction
               *   if it is less than the one found
               */
              if (min_left != min)
                {
                  prev_frac = (int) (255 * (min_prev - min));
                  if (prev_frac == 255)
                    prev_frac = 0;
                  fraction = MIN (fraction, prev_frac);
                }
              min++;
            }

          float_tmp = distp_cur[j] = min + fraction / 256.0;

          if (float_tmp > max_iterations)
            max_iterations = float_tmp;
        }

      /*  set the dist row  */
      pixel_region_set_row (distPR, distPR->x, distPR->y + i, distPR->w, (guchar *) distp_cur);

      /*  swap pointers around  */
      tmp = distp_prev;
      distp_prev = distp_cur;
      distp_cur = tmp;

      if (progress_callback)
        {
          progress += srcPR->h;
          (* progress_callback) (0, max_progress, progress, progress_data);
        }
    }

  g_free (memory);

  return max_iterations;
}

static void
compute_border (gint16  *circ,
                guint16  xradius,
                guint16  yradius)
{
  gint32 i;
  gint32 diameter = xradius * 2 + 1;
  gdouble tmp;

  for (i = 0; i < diameter; i++)
  {
    if (i > xradius)
      tmp = (i - xradius) - 0.5;
    else if (i < xradius)
      tmp = (xradius - i) - 0.5;
    else
      tmp = 0.0;

    circ[i] = RINT (yradius / (gdouble) xradius *
                    sqrt (xradius * xradius - tmp * tmp));
  }
}

void
fatten_region (PixelRegion *src,
               gint16       xradius,
               gint16       yradius)
{
  /*
     Any bugs in this fuction are probably also in thin_region
     Blame all bugs in this function on jaycox@gimp.org
  */
  register gint32 i, j, x, y;

  guchar  **buf;  /* caches the region's pixel data */
  guchar   *out;  /* holds the new scan line we are computing */
  guchar  **max;  /* caches the largest values for each column */
  gint16   *circ; /* holds the y coords of the filter's mask */
  gint16    last_max, last_index;

  guchar   *buffer;

  if (xradius <= 0 || yradius <= 0)
    return;

  max = g_new (guchar *, src->w + 2 * xradius);
  buf = g_new (guchar *, yradius + 1);
  for (i = 0; i < yradius + 1; i++)
    {
      buf[i] = g_new (guchar, src->w);
    }
  buffer = g_new (guchar, (src->w + 2 * xradius) * (yradius + 1));
  for (i = 0; i < src->w + 2 * xradius; i++)
    {
      if (i < xradius)
        max[i] = buffer;
      else if (i < src->w + xradius)
        max[i] = &buffer[(yradius + 1) * (i - xradius)];
      else
        max[i] = &buffer[(yradius + 1) * (src->w + xradius - 1)];

      for (j = 0; j < xradius + 1; j++)
        max[i][j] = 0;
    }
  /* offset the max pointer by xradius so the range of the array
     is [-xradius] to [src->w + xradius] */
  max += xradius;

  out =  g_new (guchar, src->w);

  circ = g_new (gint16, 2 * xradius + 1);
  compute_border (circ, xradius, yradius);

  /* offset the circ pointer by xradius so the range of the array
     is [-xradius] to [xradius] */
  circ += xradius;

  memset (buf[0], 0, src->w);
  for (i = 0; i < yradius && i < src->h; i++) /* load top of image */
    pixel_region_get_row (src, src->x, src->y + i, src->w, buf[i + 1], 1);

  for (x = 0; x < src->w; x++) /* set up max for top of image */
    {
      max[x][0] = 0;         /* buf[0][x] is always 0 */
      max[x][1] = buf[1][x]; /* MAX (buf[1][x], max[x][0]) always = buf[1][x]*/
      for (j = 2; j < yradius + 1; j++)
        max[x][j] = MAX(buf[j][x], max[x][j-1]);
    }

  for (y = 0; y < src->h; y++)
    {
      rotate_pointers (buf, yradius + 1);
      if (y < src->h - (yradius))
        pixel_region_get_row (src, src->x, src->y + y + yradius, src->w,
                              buf[yradius], 1);
      else
        memset (buf[yradius], 0, src->w);
      for (x = 0; x < src->w; x++) /* update max array */
        {
          for (i = yradius; i > 0; i--)
            {
              max[x][i] = MAX (MAX (max[x][i - 1], buf[i - 1][x]), buf[i][x]);
            }
          max[x][0] = buf[0][x];
        }
      last_max = max[0][circ[-1]];
      last_index = 1;
      for (x = 0; x < src->w; x++) /* render scan line */
        {
          last_index--;
          if (last_index >= 0)
            {
              if (last_max == 255)
                out[x] = 255;
              else
                {
                  last_max = 0;
                  for (i = xradius; i >= 0; i--)
                    if (last_max < max[x + i][circ[i]])
                      {
                        last_max = max[x + i][circ[i]];
                        last_index = i;
                      }
                  out[x] = last_max;
                }
            }
          else
            {
              last_index = xradius;
              last_max = max[x + xradius][circ[xradius]];
              for (i = xradius - 1; i >= -xradius; i--)
                if (last_max < max[x + i][circ[i]])
                  {
                    last_max = max[x + i][circ[i]];
                    last_index = i;
                  }
              out[x] = last_max;
            }
        }
      pixel_region_set_row (src, src->x, src->y + y, src->w, out);
    }
  /* undo the offsets to the pointers so we can free the malloced memmory */
  circ -= xradius;
  max -= xradius;

  g_free (circ);
  g_free (buffer);
  g_free (max);
  for (i = 0; i < yradius + 1; i++)
    g_free (buf[i]);
  g_free (buf);
  g_free (out);
}

void
thin_region (PixelRegion *src,
             gint16       xradius,
             gint16       yradius,
             gboolean     edge_lock)
{
  /*
     pretty much the same as fatten_region only different
     blame all bugs in this function on jaycox@gimp.org
  */
  /* If edge_lock is true  we assume that pixels outside the region
     we are passed are identical to the edge pixels.
     If edge_lock is false, we assume that pixels outside the region are 0
  */
  register gint32 i, j, x, y;
  guchar  **buf;  /* caches the the region's pixels */
  guchar   *out;  /* holds the new scan line we are computing */
  guchar  **max;  /* caches the smallest values for each column */
  gint16   *circ; /* holds the y coords of the filter's mask */
  gint16    last_max, last_index;

  guchar   *buffer;
  gint      buffer_size;

  if (xradius <= 0 || yradius <= 0)
    return;

  max = g_new (guchar *, src->w + 2 * xradius);

  buf = g_new (guchar *, yradius + 1);
  for (i = 0; i < yradius + 1; i++)
    {
      buf[i] = g_new (guchar, src->w);
    }

  buffer_size = (src->w + 2 * xradius + 1) * (yradius + 1);
  buffer = g_new (guchar, buffer_size);
  if (edge_lock)
    memset(buffer, 255, buffer_size);
  else
    memset(buffer, 0, buffer_size);

  for (i = 0; i < src->w + 2 * xradius; i++)
    {
      if (i < xradius)
        if (edge_lock)
          max[i] = buffer;
        else
          max[i] = &buffer[(yradius + 1) * (src->w + xradius)];
      else if (i < src->w + xradius)
        max[i] = &buffer[(yradius + 1) * (i - xradius)];
      else
        if (edge_lock)
          max[i] = &buffer[(yradius + 1) * (src->w + xradius - 1)];
        else
          max[i] = &buffer[(yradius + 1) * (src->w + xradius)];
    }
  if (!edge_lock)
    for (j = 0 ; j < xradius + 1; j++)
      max[0][j] = 0;

  /* offset the max pointer by xradius so the range of the array
     is [-xradius] to [src->w + xradius] */
  max += xradius;

  out = g_new (guchar, src->w);

  circ = g_new (gint16, 2 * xradius + 1);
  compute_border(circ, xradius, yradius);

 /* offset the circ pointer by xradius so the range of the array
    is [-xradius] to [xradius] */
  circ += xradius;

  for (i = 0; i < yradius && i < src->h; i++) /* load top of image */
    pixel_region_get_row (src, src->x, src->y + i, src->w, buf[i + 1], 1);
  if (edge_lock)
    memcpy (buf[0], buf[1], src->w);
  else
    memset (buf[0], 0, src->w);


  for (x = 0; x < src->w; x++) /* set up max for top of image */
    {
      max[x][0] = buf[0][x];
      for (j = 1; j < yradius + 1; j++)
        max[x][j] = MIN(buf[j][x], max[x][j-1]);
    }

  for (y = 0; y < src->h; y++)
    {
      rotate_pointers (buf, yradius + 1);
      if (y < src->h - yradius)
        pixel_region_get_row (src, src->x, src->y + y + yradius, src->w,
                              buf[yradius], 1);
      else if (edge_lock)
        memcpy (buf[yradius], buf[yradius - 1], src->w);
      else
        memset (buf[yradius], 0, src->w);

      for (x = 0 ; x < src->w; x++) /* update max array */
        {
          for (i = yradius; i > 0; i--)
            {
              max[x][i] = MIN (MIN (max[x][i - 1], buf[i - 1][x]), buf[i][x]);
            }
          max[x][0] = buf[0][x];
        }
      last_max =  max[0][circ[-1]];
      last_index = 0;

      for (x = 0 ; x < src->w; x++) /* render scan line */
        {
          last_index--;
          if (last_index >= 0)
            {
              if (last_max == 0)
                out[x] = 0;
              else
                {
                  last_max = 255;
                  for (i = xradius; i >= 0; i--)
                    if (last_max > max[x + i][circ[i]])
                      {
                        last_max = max[x + i][circ[i]];
                        last_index = i;
                      }
                  out[x] = last_max;
                }
            }
          else
            {
              last_index = xradius;
              last_max = max[x + xradius][circ[xradius]];
              for (i = xradius - 1; i >= -xradius; i--)
                if (last_max > max[x + i][circ[i]])
                  {
                    last_max = max[x + i][circ[i]];
                    last_index = i;
                  }
              out[x] = last_max;
            }
        }
      pixel_region_set_row (src, src->x, src->y + y, src->w, out);
    }

  /* undo the offsets to the pointers so we can free the malloced memmory */
  circ -= xradius;
  max -= xradius;
  /* free the memmory */
  g_free (circ);
  g_free (buffer);
  g_free (max);
  for (i = 0; i < yradius + 1; i++)
    g_free (buf[i]);
  g_free (buf);
  g_free (out);
}

static void
compute_transition (guchar  *transition,
                    guchar **buf,
                    gint32   width)
{
  register gint32 x = 0;

  if (width == 1)
    {
      if (buf[1][x] > 127 && (buf[0][x] < 128 || buf[2][x] < 128))
        transition[x] = 255;
      else
        transition[x] = 0;
      return;
    }
  if (buf[1][x] > 127)
    {
      if ( buf[0][x] < 128 || buf[0][x + 1] < 128 ||
           buf[1][x + 1] < 128 ||
           buf[2][x] < 128 || buf[2][x + 1] < 128 )
        transition[x] = 255;
      else
        transition[x] = 0;
    }
  else
    transition[x] = 0;
  for (x = 1; x < width - 1; x++)
    {
      if (buf[1][x] >= 128)
        {
          if (buf[0][x - 1] < 128 || buf[0][x] < 128 || buf[0][x + 1] < 128 ||
              buf[1][x - 1] < 128           ||          buf[1][x + 1] < 128 ||
              buf[2][x - 1] < 128 || buf[2][x] < 128 || buf[2][x + 1] < 128)
            transition[x] = 255;
          else
            transition[x] = 0;
        }
      else
        transition[x] = 0;
    }
  if (buf[1][x] >= 128)
    {
      if ( buf[0][x - 1] < 128 || buf[0][x] < 128 ||
           buf[1][x - 1] < 128 ||
           buf[2][x - 1] < 128 || buf[2][x] < 128)
        transition[x] = 255;
      else
        transition[x] = 0;
    }
  else
    transition[x] = 0;
}

void
border_region (PixelRegion *src,
               gint16       xradius,
               gint16       yradius)
{
  /*
     This function has no bugs, but if you imagine some you can
     blame them on jaycox@gimp.org
  */
  register gint32 i, j, x, y;
  guchar  *buf[3];
  guchar  *out;
  gint16  *max;
  guchar **density;
  guchar **transition;
  guchar   last_max;
  gint16   last_index;

  if (xradius < 0 || yradius < 0)
    {
      g_warning ("border_region: negative radius specified.");
      return;
    }

  if (xradius == 0 || yradius == 0)
    {
      guchar color[] = "\0\0\0\0";

      color_region (src, color);
      return;
    }

  if (xradius == 1 && yradius == 1) /* optimize this case specifically */
    {
      guchar *transition;
      guchar *source[3];

      for (i = 0; i < 3; i++)
        source[i] = g_new (guchar, src->w);

      transition = g_new (guchar, src->w);

      pixel_region_get_row (src, src->x, src->y + 0, src->w, source[0], 1);
      memcpy (source[1], source[0], src->w);
      if (src->h > 1)
        pixel_region_get_row (src, src->x, src->y + 1, src->w, source[2], 1);
      else
        memcpy (source[2], source[1], src->w);

      compute_transition (transition, source, src->w);
      pixel_region_set_row (src, src->x, src->y , src->w, transition);

      for (y = 1; y < src->h; y++)
        {
          rotate_pointers (source, 3);
          if (y + 1 < src->h)
            pixel_region_get_row (src, src->x, src->y + y + 1, src->w,
                                  source[2], 1);
          else
            memcpy(source[2], source[1], src->w);
          compute_transition (transition, source, src->w);
          pixel_region_set_row (src, src->x, src->y + y, src->w, transition);
        }

      for (i = 0; i < 3; i++)
        g_free (source[i]);
      g_free (transition);
      return;
    }

  max = g_new (gint16, src->w + 2 * xradius);
  for (i = 0; i < (src->w + 2 * xradius); i++)
    max[i] = yradius + 2;
  max += xradius;

  for (i = 0; i < 3; i++)
    buf[i] = g_new (guchar, src->w);

  transition = g_new (guchar *, yradius + 1);
  for (i = 0; i < yradius + 1; i++)
    {
      transition[i] = g_new (guchar, src->w + 2 * xradius);
      memset(transition[i], 0, src->w + 2 * xradius);
      transition[i] += xradius;
    }
  out = g_new (guchar, src->w);
  density = g_new (guchar *, 2 * xradius + 1);
  density += xradius;

  for (x = 0; x < (xradius + 1); x++) /* allocate density[][] */
    {
      density[ x]  = g_new (guchar, 2 * yradius + 1);
      density[ x] += yradius;
      density[-x]  = density[x];
    }
  for (x = 0; x < (xradius + 1); x++) /* compute density[][] */
    {
      register gdouble tmpx, tmpy, dist;
      guchar a;

      if (x > 0)
        tmpx = x - 0.5;
      else if (x < 0)
        tmpx = x + 0.5;
      else
        tmpx = 0.0;

      for (y = 0; y < (yradius + 1); y++)
        {
          if (y > 0)
            tmpy = y - 0.5;
          else if (y < 0)
            tmpy = y + 0.5;
          else
            tmpy = 0.0;
          dist = ((tmpy * tmpy) / (yradius * yradius) +
                  (tmpx * tmpx) / (xradius * xradius));
          if (dist < 1.0)
            a = 255 * (1.0 - sqrt (dist));
          else
            a = 0;
          density[ x][ y] = a;
          density[ x][-y] = a;
          density[-x][ y] = a;
          density[-x][-y] = a;
        }
    }
  pixel_region_get_row (src, src->x, src->y + 0, src->w, buf[0], 1);
  memcpy (buf[1], buf[0], src->w);
  if (src->h > 1)
    pixel_region_get_row (src, src->x, src->y + 1, src->w, buf[2], 1);
  else
    memcpy (buf[2], buf[1], src->w);
  compute_transition (transition[1], buf, src->w);

  for (y = 1; y < yradius && y + 1 < src->h; y++) /* set up top of image */
    {
      rotate_pointers (buf, 3);
      pixel_region_get_row (src, src->x, src->y + y + 1, src->w, buf[2], 1);
      compute_transition (transition[y + 1], buf, src->w);
    }
  for (x = 0; x < src->w; x++) /* set up max[] for top of image */
    {
      max[x] = -(yradius + 7);
      for (j = 1; j < yradius + 1; j++)
        if (transition[j][x])
          {
            max[x] = j;
            break;
          }
    }
  for (y = 0; y < src->h; y++) /* main calculation loop */
    {
      rotate_pointers (buf, 3);
      rotate_pointers (transition, yradius + 1);
      if (y < src->h - (yradius + 1))
        {
          pixel_region_get_row (src, src->x, src->y + y + yradius + 1, src->w,
                                buf[2], 1);
          compute_transition (transition[yradius], buf, src->w);
        }
      else
        memcpy (transition[yradius], transition[yradius - 1], src->w);

      for (x = 0; x < src->w; x++) /* update max array */
        {
          if (max[x] < 1)
            {
              if (max[x] <= -yradius)
                {
                  if (transition[yradius][x])
                    max[x] = yradius;
                  else
                    max[x]--;
                }
              else
                if (transition[-max[x]][x])
                  max[x] = -max[x];
                else if (transition[-max[x] + 1][x])
                  max[x] = -max[x] + 1;
                else
                  max[x]--;
            }
          else
            max[x]--;
          if (max[x] < -yradius - 1)
            max[x] = -yradius - 1;
        }
      last_max =  max[0][density[-1]];
      last_index = 1;
      for (x = 0 ; x < src->w; x++) /* render scan line */
        {
          last_index--;
          if (last_index >= 0)
            {
              last_max = 0;
              for (i = xradius; i >= 0; i--)
                if (max[x + i] <= yradius && max[x + i] >= -yradius &&
                    density[i][max[x+i]] > last_max)
                  {
                    last_max = density[i][max[x + i]];
                    last_index = i;
                  }
              out[x] = last_max;
            }
          else
            {
              last_max = 0;
              for (i = xradius; i >= -xradius; i--)
                if (max[x + i] <= yradius && max[x + i] >= -yradius &&
                    density[i][max[x + i]] > last_max)
                  {
                    last_max = density[i][max[x + i]];
                    last_index = i;
                  }
              out[x] = last_max;
            }
          if (last_max == 0)
            {
              for (i = x + 1; i < src->w; i++)
                {
                  if (max[i] >= -yradius)
                    break;
                }
              if (i - x > xradius)
                {
                  for (; x < i - xradius; x++)
                    out[x] = 0;
                  x--;
                }
              last_index = xradius;
            }
        }
      pixel_region_set_row (src, src->x, src->y + y, src->w, out);
    }
  g_free (out);

  for (i = 0; i < 3; i++)
    g_free (buf[i]);

  max -= xradius;
  g_free (max);

  for (i = 0; i < yradius + 1; i++)
    {
      transition[i] -= xradius;
      g_free (transition[i]);
    }
  g_free (transition);

  for (i = 0; i < xradius + 1 ; i++)
    {
      density[i] -= yradius;
      g_free (density[i]);
    }
  density -= xradius;
  g_free (density);
}

void
swap_region (PixelRegion *src,
             PixelRegion *dest)
{
  gint h;
  gint length;
  guchar * s, * d;
  void * pr;

  for (pr = pixel_regions_register (2, src, dest);
       pr != NULL;
       pr = pixel_regions_process (pr))
    {
      s = src->data;
      h = src->h;
      d = dest->data;
      length = src->w * src->bytes;

      while (h --)
        {
          swap_pixels (s, d, length);
          s += src->rowstride;
          d += dest->rowstride;
        }
    }
}


static void
apply_mask_to_sub_region (gint        *opacityp,
                          PixelRegion *src,
                          PixelRegion *mask)
{
  gint    h;
  guchar *s;
  guchar *m;
  guint   opacity = *opacityp;

  s = src->data;
  m = mask->data;
  h = src->h;

  while (h --)
    {
      apply_mask_to_alpha_channel (s, m, opacity, src->w, src->bytes);
      s += src->rowstride;
      m += mask->rowstride;
    }
}

void
apply_mask_to_region (PixelRegion *src,
                      PixelRegion *mask,
                      guint        opacity)
{
  pixel_regions_process_parallel ((p_func)apply_mask_to_sub_region,
                                  &opacity, 2, src, mask);
}


static void
combine_mask_and_sub_region_stipple (gint        *opacityp,
                                     PixelRegion *src,
                                     PixelRegion *mask)
{
  gint    h;
  guchar *s;
  guchar *m;
  guint   opacity = *opacityp;

  s = src->data;
  m = mask->data;
  h = src->h;

  while (h --)
    {
      combine_mask_and_alpha_channel_stipple (s, m, opacity, src->w, src->bytes);
      s += src->rowstride;
      m += mask->rowstride;
    }
}


static void
combine_mask_and_sub_region_stroke (gint        *opacityp,
                                    PixelRegion *src,
                                    PixelRegion *mask)
{
  gint    h;
  guchar *s;
  guchar *m;
  guint   opacity = *opacityp;

  s = src->data;
  m = mask->data;
  h = src->h;

  while (h --)
    {
      combine_mask_and_alpha_channel_stroke (s, m, opacity, src->w, src->bytes);
      s += src->rowstride;
      m += mask->rowstride;
    }
}


void
combine_mask_and_region (PixelRegion *src,
                         PixelRegion *mask,
                         guint        opacity,
                         gboolean     stipple)
{
  if (stipple)
    pixel_regions_process_parallel ((p_func)combine_mask_and_sub_region_stipple,
                                    &opacity, 2, src, mask);
  else
    pixel_regions_process_parallel ((p_func)combine_mask_and_sub_region_stroke,
                                    &opacity, 2, src, mask);
}


void
copy_gray_to_region (PixelRegion *src,
                     PixelRegion *dest)
{
  gint    h;
  guchar *s;
  guchar *d;
  void   *pr;

  for (pr = pixel_regions_register (2, src, dest);
       pr != NULL;
       pr = pixel_regions_process (pr))
    {
      s = src->data;
      d = dest->data;
      h = src->h;

      while (h --)
        {
          copy_gray_to_inten_a_pixels (s, d, src->w, dest->bytes);
          s += src->rowstride;
          d += dest->rowstride;
        }
    }
}

void
copy_component (PixelRegion *src,
                PixelRegion *dest,
                guint        pixel)
{
  gint    h;
  guchar *s;
  guchar *d;
  void   *pr;

  for (pr = pixel_regions_register (2, src, dest);
       pr != NULL;
       pr = pixel_regions_process (pr))
    {
      s = src->data;
      d = dest->data;
      h = src->h;

      while (h --)
        {
          component_pixels (s, d, src->w, src->bytes, pixel);
          s += src->rowstride;
          d += dest->rowstride;
        }
    }
}

struct initial_regions_struct
{
  guint                 opacity;
  GimpLayerModeEffects  mode;
  const gboolean       *affect;
  InitialMode                type;
  guchar               *data;
};

void
initial_sub_region (struct initial_regions_struct *st,
                    PixelRegion                   *src,
                    PixelRegion                   *dest,
                    PixelRegion                   *mask)
{
  gint                  h;
  guchar               *s, *d, *m;
  guchar               *buf;
  guchar               *data;
  guint                 opacity;
  GimpLayerModeEffects  mode;
  const gboolean       *affect;
  InitialMode           type;

  /* use src->bytes + 1 since DISSOLVE always needs a buffer with alpha */
  buf = g_alloca (MAX (src->w * (src->bytes + 1),
                       dest->w * dest->bytes));
  data    = st->data;
  opacity = st->opacity;
  mode    = st->mode;
  affect  = st->affect;
  type    = st->type;

  s = src->data;
  d = dest->data;
  m = mask ? mask->data : NULL;

  for (h = 0; h < src->h; h++)
    {
      /*  based on the type of the initial image...  */
      switch (type)
        {
        case INITIAL_CHANNEL_MASK:
        case INITIAL_CHANNEL_SELECTION:
          initial_channel_pixels (s, d, src->w, dest->bytes);
          break;

        case INITIAL_INDEXED:
          initial_indexed_pixels (s, d, data, src->w);
          break;

        case INITIAL_INDEXED_ALPHA:
          initial_indexed_a_pixels (s, d, m, &no_mask, data, opacity, src->w);
          break;

        case INITIAL_INTENSITY:
          if (mode == GIMP_DISSOLVE_MODE)
            {
              if (gimp_composite_options.bits & GIMP_COMPOSITE_OPTION_USE)
                {
                  GimpCompositeContext ctx;

                  ctx.A = NULL;
                  ctx.pixelformat_A = GIMP_PIXELFORMAT_RGBA8;

                  ctx.B = s;
                  ctx.pixelformat_B = (src->bytes   == 1 ? GIMP_PIXELFORMAT_V8
                                       : src->bytes == 2 ? GIMP_PIXELFORMAT_VA8
                                       : src->bytes == 3 ? GIMP_PIXELFORMAT_RGB8
                                       : src->bytes == 4 ? GIMP_PIXELFORMAT_RGBA8
                                       : GIMP_PIXELFORMAT_ANY);
                  ctx.D = buf;
                  ctx.pixelformat_D = ctx.pixelformat_B;

                  ctx.M = m;

                  ctx.n_pixels = src->w;
                  ctx.op = GIMP_COMPOSITE_DISSOLVE;
                  ctx.dissolve.x = src->x;
                  ctx.dissolve.y = src->y + h;
                  ctx.dissolve.opacity = opacity;
                  gimp_composite_dispatch (&ctx);
                }
              else
                {
                  dissolve_pixels (s, m, buf, src->x, src->y + h,
                                   opacity, src->w,
                                   src->bytes, src->bytes + 1,
                                   FALSE);
                }

              initial_inten_a_pixels (buf, d, NULL, OPAQUE_OPACITY, affect,
                                      src->w, src->bytes + 1);
            }
          else
            {
              initial_inten_pixels (s, d, m, &no_mask, opacity, affect,
                                    src->w, src->bytes);
            }
          break;

        case INITIAL_INTENSITY_ALPHA:
          if (mode == GIMP_DISSOLVE_MODE)
            {
              if (gimp_composite_options.bits & GIMP_COMPOSITE_OPTION_USE)
                {
                  GimpCompositeContext ctx;

                  ctx.A = NULL;
                  ctx.pixelformat_A = GIMP_PIXELFORMAT_RGBA8;

                  ctx.B = s;
                  ctx.pixelformat_B = (src->bytes   == 1 ? GIMP_PIXELFORMAT_V8
                                       : src->bytes == 2 ? GIMP_PIXELFORMAT_VA8
                                       : src->bytes == 3 ? GIMP_PIXELFORMAT_RGB8
                                       : src->bytes == 4 ? GIMP_PIXELFORMAT_RGBA8
                                       : GIMP_PIXELFORMAT_ANY);
                  ctx.D = buf;
                  ctx.pixelformat_D = ctx.pixelformat_B;

                  ctx.M = m;

                  ctx.n_pixels = src->w;
                  ctx.op = GIMP_COMPOSITE_DISSOLVE;
                  ctx.dissolve.x = src->x;
                  ctx.dissolve.y = src->y + h;
                  ctx.dissolve.opacity = opacity;
                  gimp_composite_dispatch (&ctx);
                }
              else
                {
                  dissolve_pixels (s, m, buf, src->x, src->y + h,
                                   opacity, src->w,
                                   src->bytes, src->bytes,
                                   TRUE);
                }

              initial_inten_a_pixels (buf, d, NULL, OPAQUE_OPACITY, affect,
                                      src->w, src->bytes);
            }
          else
            {
              initial_inten_a_pixels (s, d, m, opacity, affect, src->w, src->bytes);
            }
          break;
        }

      s += src->rowstride;
      d += dest->rowstride;
      if (mask)
        m += mask->rowstride;
    }
}

void
initial_region (PixelRegion          *src,
                PixelRegion          *dest,
                PixelRegion          *mask,
                guchar               *data,
                guint                 opacity,
                GimpLayerModeEffects  mode,
                const gboolean       *affect,
                InitialMode           type)
{
  struct initial_regions_struct st;

  st.opacity = opacity;
  st.mode    = mode;
  st.affect  = affect;
  st.type    = type;
  st.data    = data;

  pixel_regions_process_parallel ((p_func)initial_sub_region, &st, 3,
                                    src, dest, mask);
}

struct combine_regions_struct
{
  guint                 opacity;
  GimpLayerModeEffects  mode;
  const gboolean       *affect;
  CombinationMode       type;
  guchar               *data;
  gboolean              opacity_quickskip_possible;
  gboolean              transparency_quickskip_possible;
};

static inline CombinationMode
apply_indexed_layer_mode (guchar                *src1,
                          guchar                *src2,
                          guchar               **dest,
                          GimpLayerModeEffects   mode,
                          CombinationMode        cmode)
{
  /*  assumes we're applying src2 TO src1  */
  switch (mode)
    {
    case GIMP_REPLACE_MODE:
      *dest = src2;
      cmode = REPLACE_INDEXED;
      break;

    case GIMP_BEHIND_MODE:
      *dest = src2;
      if (cmode == COMBINE_INDEXED_A_INDEXED_A)
        cmode = BEHIND_INDEXED;
      else
        cmode = NO_COMBINATION;
      break;

    case GIMP_ERASE_MODE:
      *dest = src2;
      /*  If both sources have alpha channels, call erase function.
       *  Otherwise, just combine in the normal manner
       */
      cmode = (cmode == COMBINE_INDEXED_A_INDEXED_A) ? ERASE_INDEXED : cmode;
      break;

    default:
      break;
    }

  return cmode;
}

static void
combine_sub_region (struct combine_regions_struct *st,
                    PixelRegion                   *src1,
                    PixelRegion                   *src2,
                    PixelRegion                   *dest,
                    PixelRegion                   *mask)
{
  guchar               *data;
  guint                 opacity;
  guint                 layer_mode_opacity;
  guchar               *layer_mode_mask;
  GimpLayerModeEffects  mode;
  const gboolean       *affect;
  guint                 h;
  CombinationMode       combine = NO_COMBINATION;
  CombinationMode       type;
  gboolean              mode_affect = FALSE;
  guchar               *s, *s1, *s2;
  guchar               *d, *m;
  guchar               *buf;
  gboolean              opacity_quickskip_possible;
  gboolean              transparency_quickskip_possible;
  TileRowHint           hint;

  /* use src2->bytes + 1 since DISSOLVE always needs a buffer with alpha */
  buf = g_alloca (MAX (MAX (src1->w * src1->bytes,
                            src2->w * (src2->bytes + 1)),
                       dest->w * dest->bytes));

  opacity    = st->opacity;
  mode       = st->mode;
  affect     = st->affect;
  type       = st->type;
  data       = st->data;

  opacity_quickskip_possible = (st->opacity_quickskip_possible &&
                                src2->tiles);
  transparency_quickskip_possible = (st->transparency_quickskip_possible &&
                                     src2->tiles);

  s1 = src1->data;
  s2 = src2->data;
  d = dest->data;
  m = mask ? mask->data : NULL;

  if (transparency_quickskip_possible || opacity_quickskip_possible)
    {
#ifdef HINTS_SANITY
      if (src1->h != src2->h)
        g_error("HEIGHTS SUCK!!");
      if (src1->offy != dest->offy)
        g_error("SRC1 OFFSET != DEST OFFSET");
#endif
      update_tile_rowhints (src2->curtile,
                            src2->offy, src2->offy + (src1->h - 1));
    }
  /* else it's probably a brush-composite */

  /*  use separate variables for the combining opacity and the opacity
   *  the layer mode is applied with since DISSLOVE_MODE "consumes"
   *  all opacity and wants to be applied OPAQUE
   */
  layer_mode_opacity = opacity;
  layer_mode_mask    = m;

  if (mode == GIMP_DISSOLVE_MODE)
    {
      opacity = OPAQUE_OPACITY;
      m       = NULL;
    }

  for (h = 0; h < src1->h; h++)
    {
      hint = TILEROWHINT_UNDEFINED;

      if (transparency_quickskip_possible)
        {
          hint = tile_get_rowhint (src2->curtile, (src2->offy + h));

          if (hint == TILEROWHINT_TRANSPARENT)
            {
              goto next_row;
            }
        }
      else
        {
          if (opacity_quickskip_possible)
            {
              hint = tile_get_rowhint (src2->curtile, (src2->offy + h));
            }
        }

      s = buf;

      /*  apply the paint mode based on the combination type & mode  */
      switch (type)
        {
        case COMBINE_INTEN_A_INDEXED_A:
        case COMBINE_INTEN_A_CHANNEL_MASK:
        case COMBINE_INTEN_A_CHANNEL_SELECTION:
          combine = type;
          break;

        case COMBINE_INDEXED_INDEXED:
        case COMBINE_INDEXED_INDEXED_A:
        case COMBINE_INDEXED_A_INDEXED_A:
          /*  Now, apply the paint mode--for indexed images  */
          combine = apply_indexed_layer_mode (s1, s2, &s, mode, type);
          break;

        case COMBINE_INTEN_INTEN_A:
        case COMBINE_INTEN_A_INTEN:
        case COMBINE_INTEN_INTEN:
        case COMBINE_INTEN_A_INTEN_A:
          {
            /*  Now, apply the paint mode  */

            if (gimp_composite_options.bits & GIMP_COMPOSITE_OPTION_USE)
              {
                GimpCompositeContext ctx;

                ctx.A             = s1;
                ctx.pixelformat_A = (src1->bytes == 1 ? GIMP_PIXELFORMAT_V8    :
                                     src1->bytes == 2 ? GIMP_PIXELFORMAT_VA8   :
                                     src1->bytes == 3 ? GIMP_PIXELFORMAT_RGB8  :
                                     src1->bytes == 4 ? GIMP_PIXELFORMAT_RGBA8 :
                                     GIMP_PIXELFORMAT_ANY);

                ctx.B             = s2;
                ctx.pixelformat_B = (src2->bytes == 1 ? GIMP_PIXELFORMAT_V8    :
                                     src2->bytes == 2 ? GIMP_PIXELFORMAT_VA8   :
                                     src2->bytes == 3 ? GIMP_PIXELFORMAT_RGB8  :
                                     src2->bytes == 4 ? GIMP_PIXELFORMAT_RGBA8 :
                                     GIMP_PIXELFORMAT_ANY);

                ctx.D             = s;
                ctx.pixelformat_D = ctx.pixelformat_A;

                ctx.M             = layer_mode_mask;
                ctx.pixelformat_M = GIMP_PIXELFORMAT_ANY;

                ctx.n_pixels      = src1->w;
                ctx.combine       = combine;
                ctx.op            = mode;

                ctx.dissolve.x       = src1->x;
                ctx.dissolve.y       = src1->y + h;
                ctx.dissolve.opacity = layer_mode_opacity;

                mode_affect = gimp_composite_operation_effects[mode].affect_opacity;
                gimp_composite_dispatch (&ctx);
                s = ctx.D;
                combine = (ctx.combine == NO_COMBINATION) ? type : ctx.combine;
              }
            else
              {
                struct apply_layer_mode_struct alms;

                alms.src1    = s1;
                alms.src2    = s2;
                alms.mask    = layer_mode_mask;
                alms.dest    = &s;
                alms.x       = src1->x;
                alms.y       = src1->y + h;
                alms.opacity = layer_mode_opacity;
                alms.combine = combine;
                alms.length  = src1->w;
                alms.bytes1  = src1->bytes;
                alms.bytes2  = src2->bytes;

                /*  Determine whether the alpha channel of the destination
                 *  can be affected by the specified mode. -- This keeps
                 *  consistency with varying opacities.
                 */
                mode_affect = layer_modes[mode].affect_alpha;

                layer_mode_funcs[mode] (&alms);

                combine = (alms.combine == NO_COMBINATION ?
                           type : alms.combine);
              }
          }
          break;

        default:
          g_warning ("combine_sub_region: unhandled combine-type.");
          break;
        }

      /*  based on the type of the initial image...  */
      switch (combine)
        {
        case COMBINE_INDEXED_INDEXED:
          combine_indexed_and_indexed_pixels (s1, s2, d, m, opacity,
                                              affect, src1->w,
                                              src1->bytes);
          break;

        case COMBINE_INDEXED_INDEXED_A:
          combine_indexed_and_indexed_a_pixels (s1, s2, d, m, opacity,
                                                affect, src1->w,
                                                src1->bytes);
          break;

        case COMBINE_INDEXED_A_INDEXED_A:
          combine_indexed_a_and_indexed_a_pixels (s1, s2, d, m, opacity,
                                                  affect, src1->w,
                                                  src1->bytes);
          break;

        case COMBINE_INTEN_A_INDEXED_A:
          /*  assume the data passed to this procedure is the
           *  indexed layer's colormap
           */
          combine_inten_a_and_indexed_a_pixels (s1, s2, d, m, data, opacity,
                                                src1->w, dest->bytes);
          break;

        case COMBINE_INTEN_A_CHANNEL_MASK:
          /*  assume the data passed to this procedure is the
           *  indexed layer's colormap
           */
          combine_inten_a_and_channel_mask_pixels (s1, s2, d, data, opacity,
                                                   src1->w, dest->bytes);
          break;

        case COMBINE_INTEN_A_CHANNEL_SELECTION:
          combine_inten_a_and_channel_selection_pixels (s1, s2, d, data,
                                                        opacity,
                                                        src1->w,
                                                        src1->bytes);
          break;

        case COMBINE_INTEN_INTEN:
          if ((hint == TILEROWHINT_OPAQUE) &&
              opacity_quickskip_possible)
            {
              memcpy (d, s, dest->w * dest->bytes);
            }
          else
            combine_inten_and_inten_pixels (s1, s, d, m, opacity,
                                            affect, src1->w, src1->bytes);
          break;

        case COMBINE_INTEN_INTEN_A:
          combine_inten_and_inten_a_pixels (s1, s, d, m, opacity,
                                            affect, src1->w, src1->bytes);
          break;

        case COMBINE_INTEN_A_INTEN:
          combine_inten_a_and_inten_pixels (s1, s, d, m, opacity,
                                            affect, mode_affect, src1->w,
                                            src1->bytes);
          break;

        case COMBINE_INTEN_A_INTEN_A:
          if ((hint == TILEROWHINT_OPAQUE) &&
              opacity_quickskip_possible)
            {
              memcpy (d, s, dest->w * dest->bytes);
            }
          else
            combine_inten_a_and_inten_a_pixels (s1, s, d, m, opacity,
                                                affect, mode_affect,
                                                src1->w, src1->bytes);
          break;

        case BEHIND_INTEN:
          behind_inten_pixels (s1, s, d, m, opacity,
                               affect, src1->w, src1->bytes,
                               src2->bytes);
          break;

        case BEHIND_INDEXED:
          behind_indexed_pixels (s1, s, d, m, opacity,
                                 affect, src1->w, src1->bytes,
                                 src2->bytes);
          break;

        case REPLACE_INTEN:
          replace_inten_pixels (s1, s, d, m, opacity,
                                affect, src1->w, src1->bytes,
                                src2->bytes);
          break;

        case REPLACE_INDEXED:
          replace_indexed_pixels (s1, s, d, m, opacity,
                                  affect, src1->w, src1->bytes,
                                  src2->bytes);
          break;

        case ERASE_INTEN:
          erase_inten_pixels (s1, s, d, m, opacity,
                              affect, src1->w, src1->bytes);
          break;

        case ERASE_INDEXED:
          erase_indexed_pixels (s1, s, d, m, opacity,
                                affect, src1->w, src1->bytes);
          break;

        case ANTI_ERASE_INTEN:
          anti_erase_inten_pixels (s1, s, d, m, opacity,
                                   affect, src1->w, src1->bytes);
          break;

        case ANTI_ERASE_INDEXED:
          anti_erase_indexed_pixels (s1, s, d, m, opacity,
                                     affect, src1->w, src1->bytes);
          break;

        case COLOR_ERASE_INTEN:
          color_erase_inten_pixels (s1, s, d, m, opacity,
                                    affect, src1->w, src1->bytes);
          break;

        case NO_COMBINATION:
          g_warning("NO_COMBINATION");
          break;

        default:
          g_warning("UNKNOWN COMBINATION: %d", combine);
          break;
        }

    next_row:
      s1 += src1->rowstride;
      s2 += src2->rowstride;
      d += dest->rowstride;
      if (mask)
        {
          layer_mode_mask += mask->rowstride;

          if (m)
            m += mask->rowstride;
        }
    }
}


void
combine_regions (PixelRegion          *src1,
                 PixelRegion          *src2,
                 PixelRegion          *dest,
                 PixelRegion          *mask,
                 guchar               *data,
                 guint                 opacity,
                 GimpLayerModeEffects  mode,
                 const gboolean       *affect,
                 CombinationMode       type)
{
  gboolean has_alpha1, has_alpha2;
  guint i;
  struct combine_regions_struct st;

  /*  Determine which sources have alpha channels  */
  switch (type)
    {
    case COMBINE_INTEN_INTEN:
    case COMBINE_INDEXED_INDEXED:
      has_alpha1 = has_alpha2 = FALSE;
      break;
    case COMBINE_INTEN_A_INTEN:
      has_alpha1 = TRUE;
      has_alpha2 = FALSE;
      break;
    case COMBINE_INTEN_INTEN_A:
    case COMBINE_INDEXED_INDEXED_A:
      has_alpha1 = FALSE;
      has_alpha2 = TRUE;
      break;
    case COMBINE_INTEN_A_INTEN_A:
    case COMBINE_INDEXED_A_INDEXED_A:
      has_alpha1 = has_alpha2 = TRUE;
      break;
    default:
      has_alpha1 = has_alpha2 = FALSE;
    }

  st.opacity    = opacity;
  st.mode       = mode;
  st.affect     = affect;
  st.type       = type;
  st.data       = data;

  /* cheap and easy when the row of src2 is completely opaque/transparent
     and the wind is otherwise blowing in the right direction.
  */

  /* First check - we can't do an opacity quickskip if the drawable
     has a mask, or non-full opacity, or the layer mode dictates
     that we might gain transparency.
  */
  st.opacity_quickskip_possible = ((!mask)                               &&
                                   (opacity == 255)                      &&
                                   (!layer_modes[mode].decrease_opacity) &&
                                   (layer_modes[mode].affect_alpha       &&
                                    has_alpha1                           &&
                                    affect[src1->bytes - 1]));

  /* Second check - if any single colour channel can't be affected,
     we can't use the opacity quickskip.
  */
  if (st.opacity_quickskip_possible)
    {
      for (i = 0; i < src1->bytes - 1; i++)
        {
          if (!affect[i])
            {
              st.opacity_quickskip_possible = FALSE;
              break;
            }
        }
    }

  /* transparency quickskip is only possible if the layer mode
     dictates that we cannot possibly gain opacity, or the 'overall'
     opacity of the layer is set to zero anyway.
   */
  st.transparency_quickskip_possible = ((!layer_modes[mode].increase_opacity)
                                        || (opacity==0));

  /* Start the actual processing.
   */
  pixel_regions_process_parallel ((p_func)combine_sub_region, &st, 4,
                                    src1, src2, dest, mask);
}

void
combine_regions_replace (PixelRegion     *src1,
                         PixelRegion     *src2,
                         PixelRegion     *dest,
                         PixelRegion     *mask,
                         guchar          *data,
                         guint            opacity,
                         const gboolean  *affect,
                         CombinationMode  type)
{
  guint    h;
  guchar  *s1;
  guchar  *s2;
  guchar  *d;
  guchar  *m;
  gpointer pr;

  for (pr = pixel_regions_register (4, src1, src2, dest, mask);
       pr != NULL;
       pr = pixel_regions_process (pr))
    {
      s1 = src1->data;
      s2 = src2->data;
      d = dest->data;
      m = mask->data;

      for (h = 0; h < src1->h; h++)
        {

          /*  Now, apply the paint mode  */
          apply_layer_mode_replace (s1, s2, d, m, src1->x, src1->y + h,
                                    opacity, src1->w,
                                    src1->bytes, src2->bytes, affect);

          s1 += src1->rowstride;
          s2 += src2->rowstride;
          d += dest->rowstride;
          m += mask->rowstride;
        }
    }
}

static void
apply_layer_mode_replace (guchar         *src1,
                          guchar         *src2,
                          guchar         *dest,
                          guchar         *mask,
                          gint            x,
                          gint            y,
                          guint           opacity,
                          guint           length,
                          guint           bytes1,
                          guint           bytes2,
                          const gboolean *affect)
{
  replace_pixels (src1, src2, dest, mask, length, opacity, affect, bytes1, bytes2);
}
