/* LIBGIMP - The GIMP Library
 * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
 *
 * gimppixelrgn.c
 *
 * This library is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see
 * <http://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <string.h>
#include <stdarg.h>

#define GIMP_DISABLE_DEPRECATION_WARNINGS

#include "gimp.h"


/**
 * SECTION: gimppixelrgn
 * @title: gimppixelrgn
 * @short_description: Functions for operating on pixel regions.
 *
 * Functions for operating on pixel regions. These functions provide
 * fast ways of accessing and modifying portions of a drawable.
 **/


#define TILE_WIDTH  gimp_tile_width()
#define TILE_HEIGHT gimp_tile_height()


typedef struct _GimpPixelRgnHolder    GimpPixelRgnHolder;
typedef struct _GimpPixelRgnIterator  GimpPixelRgnIterator;

struct _GimpPixelRgnHolder
{
  GimpPixelRgn *pr;
  guchar       *original_data;
  gint          startx;
  gint          starty;
  gint          count;
};

struct _GimpPixelRgnIterator
{
  GSList *pixel_regions;
  gint    region_width;
  gint    region_height;
  gint    portion_width;
  gint    portion_height;
  gint    process_count;
};


static gint     gimp_get_portion_width    (GimpPixelRgnIterator *pri);
static gint     gimp_get_portion_height   (GimpPixelRgnIterator *pri);
static gpointer gimp_pixel_rgns_configure (GimpPixelRgnIterator *pri);
static void     gimp_pixel_rgn_configure  (GimpPixelRgnHolder   *prh,
                                           GimpPixelRgnIterator *pri);

/**
 * gimp_pixel_rgn_init:
 * @pr:        a pointer to a #GimpPixelRgn variable.
 * @drawable:  the #GimpDrawable the new region will be attached to.
 * @x:         the x coordinate of the top-left pixel of the region in the
 *             @drawable.
 * @y:         the y coordinate of the top-left pixel of the region in the
 *             @drawable.
 * @width:     the width of the region.
 * @height:    the height of the region.
 * @dirty:     a #gboolean indicating whether the @drawable should be marked
 *             as "dirty".
 * @shadow:    a #gboolean indicating whether the region is attached to the
 *             shadow tiles or the real @drawable tiles.
 *
 * Initialize the pixel region pointed by @pr with the specified parameters.
 *
 * The @dirty and @shadow flags can be used as follows:
 *
 * - @dirty = FALSE, @shadow = FALSE: the region will be used to read
 *                                    the actual drawable datas.  This
 *                                    is useful for save plug-ins or for
 *                                    filters.
 *
 * - @dirty = FALSE, @shadow = TRUE:  the region will be used to read the
 *                                    shadow tiles.  This is used in
 *                                    some filter plug-ins which operate
 *                                    in two passes such as gaussian
 *                                    blur.  The first pass reads the
 *                                    actual drawable data and writes to
 *                                    the shadow tiles, and the second
 *                                    one reads from and writes to the
 *                                    shadow tiles.
 *
 * - @dirty = TRUE, @shadow = TRUE:   the region will be used to write to
 *                                    the shadow tiles. It is common
 *                                    practice to write to the shadow
 *                                    tiles and then use
 *                                    gimp_drawable_merge_shadow() to
 *                                    merge the changes from the shadow
 *                                    tiles using the current selection
 *                                    as a mask.
 *
 * - @dirty = TRUE, @shadow = FALSE:  the region will be used to directly
 *                                    change the drawable content. Don't
 *                                    do this, since this could prevent
 *                                    the Undo-System from working as
 *                                    expected.
 **/
void
gimp_pixel_rgn_init (GimpPixelRgn *pr,
                     GimpDrawable *drawable,
                     gint          x,
                     gint          y,
                     gint          width,
                     gint          height,
                     gboolean      dirty,
                     gboolean      shadow)
{
  g_return_if_fail (pr != NULL);
  g_return_if_fail (drawable != NULL);
  g_return_if_fail (x >= 0 && x + width  <= drawable->width);
  g_return_if_fail (y >= 0 && y + height <= drawable->height);

  pr->data      = NULL;
  pr->drawable  = drawable;
  pr->bpp       = drawable->bpp;
  pr->rowstride = 0;
  pr->x         = x;
  pr->y         = y;
  pr->w         = width;
  pr->h         = height;
  pr->dirty     = dirty;
  pr->shadow    = shadow;
}

/**
 * gimp_pixel_rgn_resize:
 * @pr:      a pointer to a previously initialized #GimpPixelRgn.
 * @x:       the x coordinate of the new position of the region's
 *           top-left corner.
 * @y:       the y coordinate of the new position of the region's
 *           top-left corner.
 * @width:   the new width of the region.
 * @height:  the new height of the region.
 *
 * Change the position and size of a previously initialized pixel region.
 **/
void
gimp_pixel_rgn_resize (GimpPixelRgn *pr,
                       gint          x,
                       gint          y,
                       gint          width,
                       gint          height)
{
  g_return_if_fail (pr != NULL && pr->drawable != NULL);
  g_return_if_fail (x >= 0 && x + width  <= pr->drawable->width);
  g_return_if_fail (y >= 0 && y + height <= pr->drawable->height);

  pr->x = x;
  pr->y = y;
  pr->w = width;
  pr->h = height;
}

/**
 * gimp_pixel_rgn_get_pixel:
 * @pr:    a pointer to a previously initialized #GimpPixelRgn.
 * @buf:   a pointer to an array of #guchar
 * @x:     the x coordinate of the wanted pixel (relative to the drawable)
 * @y:     the y coordinate of the wanted pixel (relative to the drawable)
 *
 * Fill the buffer pointed by @buf with the value of the pixel at (@x, @y)
 * in the region @pr. @buf should be large enough to hold the pixel value
 * (1 #guchar for an indexed or grayscale drawable, 2 #guchar for
 * indexed with alpha or grayscale with alpha drawable, 3 #guchar for
 * rgb drawable and 4 #guchar for rgb with alpha drawable.
 **/
void
gimp_pixel_rgn_get_pixel (GimpPixelRgn *pr,
                          guchar       *buf,
                          gint          x,
                          gint          y)
{
  GimpTile     *tile;
  const guchar *tile_data;
  gint          b;

  g_return_if_fail (pr != NULL && pr->drawable != NULL);
  g_return_if_fail (x >= 0 && x < pr->drawable->width);
  g_return_if_fail (y >= 0 && y < pr->drawable->height);

  tile = gimp_drawable_get_tile2 (pr->drawable, pr->shadow, x, y);
  gimp_tile_ref (tile);

  tile_data = (tile->data +
               tile->bpp * (tile->ewidth * (y % TILE_HEIGHT) + (x % TILE_WIDTH)));

  for (b = 0; b < tile->bpp; b++)
    *buf++ = *tile_data++;

  gimp_tile_unref (tile, FALSE);
}

/**
 * gimp_pixel_rgn_get_row:
 * @pr:     a pointer to a previously initialized #GimpPixelRgn.
 * @buf:    a pointer to an array of #guchar
 * @x:      the x coordinate of the first pixel (relative to the drawable).
 * @y:      the y coordinate of the first pixel (relative to the drawable).
 * @width:  the number of pixels to get.
 *
 * Get several pixels of a region in a row. This function fills the buffer
 * @buf with the values of the pixels from (@x, @y) to (@x+@width-1, @y).
 * @buf should be large enough to hold all these values.
 **/
void
gimp_pixel_rgn_get_row (GimpPixelRgn *pr,
                        guchar       *buf,
                        gint          x,
                        gint          y,
                        gint          width)
{
  gint end;

  g_return_if_fail (pr != NULL && pr->drawable != NULL);
  g_return_if_fail (buf != NULL);
  g_return_if_fail (x >= 0 && x + width <= pr->drawable->width);
  g_return_if_fail (y >= 0 && y < pr->drawable->height);
  g_return_if_fail (width >= 0);

  end = x + width;

  while (x < end)
    {
      GimpTile     *tile;
      const guchar *tile_data;
      gint          inc, min;
      gint          boundary;

      tile = gimp_drawable_get_tile2 (pr->drawable, pr->shadow, x, y);
      gimp_tile_ref (tile);

      tile_data = (tile->data +
                   tile->bpp * (tile->ewidth * (y % TILE_HEIGHT) + (x % TILE_WIDTH)));

      boundary = x + (tile->ewidth - (x % TILE_WIDTH));

      min = MIN (end, boundary);
      inc = tile->bpp * (min - x);

      memcpy (buf, tile_data, inc);

      x = min;
      buf += inc;

      gimp_tile_unref (tile, FALSE);
    }
}

/**
 * gimp_pixel_rgn_get_col:
 * @pr:     a pointer to a previously initialized #GimpPixelRgn.
 * @buf:    a pointer to an array of #guchar
 * @x:      the x coordinate of the first pixel (relative to the drawable).
 * @y:      the y coordinate of the first pixel (relative to the drawable).
 * @height: the number of pixels to get.
 *
 * Get several pixels of a region's column. This function fills the buffer
 * @buf with the values of the pixels from (@x, @y) to (@x, @y+@height-1).
 * @buf should be large enough to hold all these values.
 *
 **/
void
gimp_pixel_rgn_get_col (GimpPixelRgn *pr,
                        guchar       *buf,
                        gint          x,
                        gint          y,
                        gint          height)
{
  gint end;

  g_return_if_fail (pr != NULL && pr->drawable != NULL);
  g_return_if_fail (buf != NULL);
  g_return_if_fail (x >= 0 && x < pr->drawable->width);
  g_return_if_fail (y >= 0 && y + height <= pr->drawable->height);
  g_return_if_fail (height >= 0);

  end = y + height;

  while (y < end)
    {
      GimpTile     *tile;
      const guchar *tile_data;
      gint          inc;
      gint          boundary;
      gint          b;

      tile = gimp_drawable_get_tile2 (pr->drawable, pr->shadow, x, y);
      gimp_tile_ref (tile);

      tile_data = (tile->data +
                   tile->bpp * (tile->ewidth * (y % TILE_HEIGHT) + (x % TILE_WIDTH)));

      boundary = y + (tile->eheight - (y % TILE_HEIGHT));
      inc = tile->bpp * tile->ewidth;

      for ( ; y < end && y < boundary; y++)
        {
          for (b = 0; b < tile->bpp; b++)
            *buf++ = tile_data[b];

          tile_data += inc;
        }

      gimp_tile_unref (tile, FALSE);
    }
}

/**
 * gimp_pixel_rgn_get_rect:
 * @pr:     a pointer to a previously initialized #GimpPixelRgn.
 * @buf:    a pointer to an array of #guchar
 * @x:      the x coordinate of the first pixel (relative to the drawable).
 * @y:      the y coordinate of the first pixel (relative to the drawable).
 * @width:  the width of the rectangle.
 * @height: the height of the rectangle.
 *
 * Get all the pixel values from the rectangle defined by @x, @y, @width and
 * @height. This function fills the buffer @buf with the values of the pixels
 * from (@x, @y) to (@x+@width-1, @y+@height-1).
 * @buf should be large enough to hold all these values (@width*@height*bpp).
 **/
void
gimp_pixel_rgn_get_rect (GimpPixelRgn *pr,
                         guchar       *buf,
                         gint          x,
                         gint          y,
                         gint          width,
                         gint          height)
{
  gulong  bufstride;
  gint    xstart, ystart;
  gint    xend, yend;
  gint    xboundary;
  gint    yboundary;
  gint    xstep, ystep;
  gint    ty, bpp;

  g_return_if_fail (pr != NULL && pr->drawable != NULL);
  g_return_if_fail (buf != NULL);
  g_return_if_fail (x >= 0 && x + width  <= pr->drawable->width);
  g_return_if_fail (y >= 0 && y + height <= pr->drawable->height);
  g_return_if_fail (width >= 0);
  g_return_if_fail (height >= 0);

  bpp = pr->bpp;
  bufstride = bpp * width;

  xstart = x;
  ystart = y;
  xend = x + width;
  yend = y + height;
  ystep = 0;

  while (y < yend)
    {
      x = xstart;

      while (x < xend)
        {
          GimpTile *tile;

          tile = gimp_drawable_get_tile2 (pr->drawable, pr->shadow, x, y);
          gimp_tile_ref (tile);

          xstep = tile->ewidth - (x % TILE_WIDTH);
          ystep = tile->eheight - (y % TILE_HEIGHT);
          xboundary = x + xstep;
          yboundary = y + ystep;
          xboundary = MIN (xboundary, xend);
          yboundary = MIN (yboundary, yend);

          for (ty = y; ty < yboundary; ty++)
            {
              const guchar *src;
              guchar       *dest;

              src = (tile->data +
                     tile->bpp * (tile->ewidth * (ty % TILE_HEIGHT) + (x % TILE_WIDTH)));
              dest = buf + bufstride * (ty - ystart) + bpp * (x - xstart);

              memcpy (dest, src, (xboundary - x) * bpp);
            }

          gimp_tile_unref (tile, FALSE);
          x += xstep;
        }

      y += ystep;
    }
}

/**
 * gimp_pixel_rgn_set_pixel:
 * @pr:   a pointer to a previously initialized #GimpPixelRgn.
 * @buf:  a pointer to an array of #guchar.
 * @x:    the x coordinate of the pixel (relative to the drawable).
 * @y:    the y coordinate of the pixel (relative to the drawable).
 *
 * Set the pixel at (@x, @y) to the values from @buf.
 **/
void
gimp_pixel_rgn_set_pixel (GimpPixelRgn *pr,
                          const guchar *buf,
                          gint          x,
                          gint          y)
{
  GimpTile *tile;
  guchar   *tile_data;
  gint      b;

  g_return_if_fail (pr != NULL && pr->drawable != NULL);
  g_return_if_fail (buf != NULL);
  g_return_if_fail (x >= 0 && x < pr->drawable->width);
  g_return_if_fail (y >= 0 && y < pr->drawable->height);

  tile = gimp_drawable_get_tile2 (pr->drawable, pr->shadow, x, y);
  gimp_tile_ref (tile);

  tile_data = tile->data + tile->bpp * (tile->ewidth *
                                        (y % TILE_HEIGHT) + (x % TILE_WIDTH));

  for (b = 0; b < tile->bpp; b++)
    *tile_data++ = *buf++;

  gimp_tile_unref (tile, TRUE);
}

/**
 * gimp_pixel_rgn_set_row:
 * @pr:     a pointer to a previously initialized #GimpPixelRgn.
 * @buf:    a pointer to an array of #guchar
 * @x:      the x coordinate of the first pixel (relative to the drawable).
 * @y:      the y coordinate of the first pixel (relative to the drawable).
 * @width:  the number of pixels to set.
 *
 * Set several pixels of a region in a row. This function draws the pixels
 * from (@x, @y) to (@x+@width-1, @y) using the values of the buffer @buf.
 * @buf should be large enough to hold all these values.
 **/
void
gimp_pixel_rgn_set_row (GimpPixelRgn *pr,
                        const guchar *buf,
                        gint          x,
                        gint          y,
                        gint          width)
{
  GimpTile *tile;
  guchar   *tile_data;
  gint      inc, min;
  gint      end;
  gint      boundary;

  g_return_if_fail (pr != NULL && pr->drawable != NULL);
  g_return_if_fail (buf != NULL);
  g_return_if_fail (x >= 0 && x + width <= pr->drawable->width);
  g_return_if_fail (y >= 0 && y < pr->drawable->height);
  g_return_if_fail (width >= 0);

  end = x + width;

  while (x < end)
    {
      tile = gimp_drawable_get_tile2 (pr->drawable, pr->shadow, x, y);
      gimp_tile_ref (tile);

      tile_data = (tile->data +
                   tile->bpp * (tile->ewidth * (y % TILE_HEIGHT) + (x % TILE_WIDTH)));

      boundary = x + (tile->ewidth - (x % TILE_WIDTH));

      min = MIN (end, boundary);
      inc = tile->bpp * (min - x);

      memcpy (tile_data, buf, inc);

      x = min;
      buf += inc;

      gimp_tile_unref (tile, TRUE);
    }
}

/**
 * gimp_pixel_rgn_set_col:
 * @pr:     a pointer to a previously initialized #GimpPixelRgn.
 * @buf:    a pointer to an array of #guchar
 * @x:      the x coordinate of the first pixel (relative to the drawable).
 * @y:      the y coordinate of the first pixel (relative to the drawable).
 * @height: the number of pixels to set.
 *
 * Set several pixels of a region's column. This function draws the pixels
 * from (@x, @y) to (@x, @y+@height-1) using the values from the buffer @buf.
 * @buf should be large enough to hold all these values.
 **/
void
gimp_pixel_rgn_set_col (GimpPixelRgn *pr,
                        const guchar *buf,
                        gint          x,
                        gint          y,
                        gint          height)
{
  gint      end;

  g_return_if_fail (pr != NULL && pr->drawable != NULL);
  g_return_if_fail (buf != NULL);
  g_return_if_fail (x >= 0 && x < pr->drawable->width);
  g_return_if_fail (y >= 0 && y + height <= pr->drawable->height);
  g_return_if_fail (height >= 0);

  end = y + height;

  while (y < end)
    {
      GimpTile *tile;
      guchar   *tile_data;
      gint      inc;
      gint      boundary;

      tile = gimp_drawable_get_tile2 (pr->drawable, pr->shadow, x, y);
      gimp_tile_ref (tile);

      tile_data = (tile->data +
                   tile->bpp * (tile->ewidth * (y % TILE_HEIGHT) + (x % TILE_WIDTH)));

      boundary = y + (tile->eheight - (y % TILE_HEIGHT));
      inc = tile->bpp * tile->ewidth;

      for ( ; y < end && y < boundary; y++)
        {
          gint b;

          for (b = 0; b < tile->bpp; b++)
            tile_data[b] = *buf++;

          tile_data += inc;
        }

      gimp_tile_unref (tile, TRUE);
    }
}

/**
 * gimp_pixel_rgn_set_rect:
 * @pr:     a pointer to a previously initialized #GimpPixelRgn.
 * @buf:    a pointer to an array of #guchar
 * @x:      the x coordinate of the first pixel (relative to the drawable).
 * @y:      the y coordinate of the first pixel (relative to the drawable).
 * @width:  the width of the rectangle.
 * @height: the height of the rectangle.
 *
 * Set all the pixel of the rectangle defined by @x, @y, @width and
 * @height. This function draws the rectangle from (@x, @y) to
 * (@x+@width-1, @y+@height-1), using the pixel values from the buffer @buf.
 * @buf should be large enough to hold all these values (@width*@height*bpp).
 **/
void
gimp_pixel_rgn_set_rect (GimpPixelRgn *pr,
                         const guchar *buf,
                         gint          x,
                         gint          y,
                         gint          width,
                         gint          height)
{
  gulong  bufstride;
  gint    xstart, ystart;
  gint    xend, yend;
  gint    xboundary;
  gint    yboundary;
  gint    xstep, ystep;
  gint    ty, bpp;

  g_return_if_fail (pr != NULL && pr->drawable != NULL);
  g_return_if_fail (buf != NULL);
  g_return_if_fail (x >= 0 && x + width  <= pr->drawable->width);
  g_return_if_fail (y >= 0 && y + height <= pr->drawable->height);
  g_return_if_fail (width >= 0);
  g_return_if_fail (height >= 0);

  bpp = pr->bpp;
  bufstride = bpp * width;

  xstart = x;
  ystart = y;
  xend = x + width;
  yend = y + height;
  ystep = 0;

  while (y < yend)
    {
      x = xstart;

      while (x < xend)
        {
          GimpTile *tile;

          tile = gimp_drawable_get_tile2 (pr->drawable, pr->shadow, x, y);
          gimp_tile_ref (tile);

          xstep = tile->ewidth - (x % TILE_WIDTH);
          ystep = tile->eheight - (y % TILE_HEIGHT);
          xboundary = x + xstep;
          yboundary = y + ystep;
          xboundary = MIN (xboundary, xend);
          yboundary = MIN (yboundary, yend);

          for (ty = y; ty < yboundary; ty++)
            {
              const guchar *src;
              guchar       *dest;

              src = buf + bufstride * (ty - ystart) + bpp * (x - xstart);
              dest = tile->data + tile->bpp * (tile->ewidth *
                                               (ty % TILE_HEIGHT) + (x % TILE_WIDTH));

              memcpy (dest, src, (xboundary - x) * bpp);
            }

          gimp_tile_unref (tile, TRUE);
          x += xstep;
        }

      y += ystep;
    }
}

/**
 * gimp_pixel_rgns_register2:
 * @nrgns: the number of regions to register.
 * @prs:   an array of @nrgns pointers to initialized #GimpPixelRgn.
 *
 * It takes a number of initialized regions of the same size and provides a
 * pixel region iterator the iterator can be used to iterate over the
 * registered pixel regions.  While iterating the registered pixel regions will
 * cover subsets of the original pixel regions, chosen for optimized access to
 * the image data.
 *
 * Note that the given regions themselves are changed by this function, so
 * they are resized to the first subsets.
 *
 * This function has to be used together with gimp_pixel_rgns_process in a loop.
 *
 * Returns: a #gpointer to a regions iterator.
 **/
gpointer
gimp_pixel_rgns_register2 (gint           nrgns,
                           GimpPixelRgn **prs)
{
  GimpPixelRgnIterator *pri;
  gboolean              found;

  g_return_val_if_fail (nrgns > 0, NULL);
  g_return_val_if_fail (prs != NULL, NULL);

  pri = g_slice_new0 (GimpPixelRgnIterator);

  found = FALSE;
  while (nrgns --)
    {
      GimpPixelRgn       *pr  = prs[nrgns];
      GimpPixelRgnHolder *prh = g_slice_new0 (GimpPixelRgnHolder);

      prh->pr = pr;

      if (pr != NULL)
        {
          /*  If there is a defined value for data, make sure tiles is NULL  */
          if (pr->data)
            pr->drawable = NULL;

          prh->original_data     = pr->data;
          prh->startx            = pr->x;
          prh->starty            = pr->y;
          prh->pr->process_count = 0;

          if (! found)
            {
              found = TRUE;
              pri->region_width  = pr->w;
              pri->region_height = pr->h;
            }
        }

      /*  Add the pixel Rgn holder to the list  */
      pri->pixel_regions = g_slist_prepend (pri->pixel_regions, prh);
    }

  return gimp_pixel_rgns_configure (pri);
}

/**
 * gimp_pixel_rgns_register:
 * @nrgns: the number of regions to register.
 * @...:   @nrgns pointers to #GimpPixelRgn.
 *
 * This is the varargs version of #gimp_pixel_rgns_register2.
 *
 * Returns: a #gpointer to a regions iterator.
 **/
gpointer
gimp_pixel_rgns_register (gint nrgns,
                          ...)
{
  GimpPixelRgn **prs;
  gint           n;
  va_list        ap;

  g_return_val_if_fail (nrgns > 0, NULL);

  prs = g_newa (GimpPixelRgn *, nrgns);

  va_start (ap, nrgns);

  for (n = nrgns; n--; )
    prs[n] = va_arg (ap, GimpPixelRgn *);

  va_end (ap);

  return gimp_pixel_rgns_register2 (nrgns, prs);
}

/**
 * gimp_pixel_rgns_process:
 * @pri_ptr: a regions iterator returned by #gimp_pixel_rgns_register,
 *           #gimp_pixel_rgns_register2 or #gimp_pixel_rgns_process.
 *
 * This function update the regions registered previously with one of the
 * #gimp_pixel_rgns_register* functions to their next tile.
 *
 * Returns: a #gpointer to a new regions iterator or #NULL if there isn't
 * any tiles left.
 **/
gpointer
gimp_pixel_rgns_process (gpointer pri_ptr)
{
  GimpPixelRgnIterator *pri;
  GSList               *list;

  g_return_val_if_fail (pri_ptr != NULL, NULL);

  pri = (GimpPixelRgnIterator*) pri_ptr;
  pri->process_count++;

  /*  Unref all referenced tiles and increment the offsets  */

  for (list = pri->pixel_regions; list; list = list->next)
    {
      GimpPixelRgnHolder *prh = list->data;

      if ((prh->pr != NULL) && (prh->pr->process_count != pri->process_count))
        {
          /*  This eliminates the possibility of incrementing the
           *  same region twice
           */
          prh->pr->process_count++;

          /*  Unref the last referenced tile if the underlying region
           *  is a tile manager
           */
          if (prh->pr->drawable)
            {
              GimpTile *tile = gimp_drawable_get_tile2 (prh->pr->drawable,
                                                        prh->pr->shadow,
                                                        prh->pr->x,
                                                        prh->pr->y);
              gimp_tile_unref (tile, prh->pr->dirty);
            }

          prh->pr->x += pri->portion_width;

          if ((prh->pr->x - prh->startx) >= pri->region_width)
            {
              prh->pr->x  = prh->startx;
              prh->pr->y += pri->portion_height;
            }
        }
    }

  return gimp_pixel_rgns_configure (pri);
}


static gint
gimp_get_portion_width (GimpPixelRgnIterator *pri)
{
  GSList *list;
  gint    min_width = G_MAXINT;
  gint    width;

  /* Find the minimum width to the next vertical tile (in the case of
   * a tile manager) or to the end of the pixel region (in the case of
   * no tile manager)
   */

  for (list = pri->pixel_regions; list; list = list->next)
    {
      GimpPixelRgnHolder *prh = list->data;

      if (prh->pr)
        {
          /* Check if we're past the point of no return  */
          if ((prh->pr->x - prh->startx) >= pri->region_width)
            return 0;

          if (prh->pr->drawable)
            {
              width = TILE_WIDTH - (prh->pr->x % TILE_WIDTH);
              width = CLAMP (width,
                             0,
                             (pri->region_width - (prh->pr->x - prh->startx)));
            }
          else
            {
              width = (pri->region_width - (prh->pr->x - prh->startx));
            }

          if (width < min_width)
            min_width = width;
        }
    }

  return min_width;
}

static gint
gimp_get_portion_height (GimpPixelRgnIterator *pri)
{
  GSList *list;
  gint    min_height = G_MAXINT;
  gint    height;

  /* Find the minimum height to the next vertical tile (in the case of
   * a tile manager) or to the end of the pixel region (in the case of
   * no tile manager)
   */

  for (list = pri->pixel_regions; list; list = list->next)
    {
      GimpPixelRgnHolder *prh = list->data;

      if (prh->pr)
        {
          /* Check if we're past the point of no return  */
          if ((prh->pr->y - prh->starty) >= pri->region_height)
            return 0;

          if (prh->pr->drawable)
            {
              height = TILE_HEIGHT - (prh->pr->y % TILE_HEIGHT);
              height = CLAMP (height,
                              0,
                              (pri->region_height - (prh->pr->y - prh->starty)));
            }
          else
            {
              height = (pri->region_height - (prh->pr->y - prh->starty));
            }

          if (height < min_height)
            min_height = height;
        }
    }

  return min_height;
}

static gpointer
gimp_pixel_rgns_configure (GimpPixelRgnIterator *pri)
{
  GSList *list;

  /*  Determine the portion width and height  */
  pri->portion_width  = gimp_get_portion_width (pri);
  pri->portion_height = gimp_get_portion_height (pri);

  if (pri->portion_width  == 0 ||
      pri->portion_height == 0)
    {
      /*  free the pixel regions list  */
      for (list = pri->pixel_regions; list; list = list->next)
        g_slice_free (GimpPixelRgnHolder, list->data);

      g_slist_free (pri->pixel_regions);
      g_slice_free (GimpPixelRgnIterator, pri);

      return NULL;
    }

  pri->process_count++;

  for (list = pri->pixel_regions; list; list = list->next)
    {
      GimpPixelRgnHolder *prh = list->data;

      if ((prh->pr != NULL) && (prh->pr->process_count != pri->process_count))
        {
          prh->pr->process_count++;
          gimp_pixel_rgn_configure (prh, pri);
        }
    }

  return pri;
}

static void
gimp_pixel_rgn_configure (GimpPixelRgnHolder   *prh,
                          GimpPixelRgnIterator *pri)
{
  /* Configure the rowstride and data pointer for the pixel region
   * based on the current offsets into the region and whether the
   * region is represented by a tile manager or not
   */
  if (prh->pr->drawable)
    {
      GimpTile *tile;
      gint      offx;
      gint      offy;

      tile = gimp_drawable_get_tile2 (prh->pr->drawable,
                                      prh->pr->shadow,
                                      prh->pr->x,
                                      prh->pr->y);
      gimp_tile_ref (tile);

      offx = prh->pr->x % TILE_WIDTH;
      offy = prh->pr->y % TILE_HEIGHT;

      prh->pr->rowstride = tile->ewidth * prh->pr->bpp;
      prh->pr->data = (tile->data +
                       offy * prh->pr->rowstride + offx * prh->pr->bpp);
    }
  else
    {
      prh->pr->data = (prh->original_data +
                       prh->pr->y * prh->pr->rowstride +
                       prh->pr->x * prh->pr->bpp);
    }

  prh->pr->w = pri->portion_width;
  prh->pr->h = pri->portion_height;
}
