/* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>

#include "../appenv.h"
#include "../boundary.h"
#include "float16.h"
#include "../rc.h"
#include "../paint_funcs_area.h"
#include "paint_funcs_row_u8.h"
#include "paint_funcs_row_u16.h"
#include "paint_funcs_row_float.h"
#include "paint_funcs_row_float16.h"
#include "../pixelarea.h"
#include "../pixelrow.h"
#include "../tag.h"

#define EPSILON            0.0001

/* only for scale_area et al. */
typedef enum
{
  MinifyX_MinifyY,
  MinifyX_MagnifyY,
  MagnifyX_MinifyY,
  MagnifyX_MagnifyY
} ScaleType;


/*  Layer modes information.  only the affect_alpha is used */
typedef struct LayerMode LayerMode;
struct LayerMode
{
  int affect_alpha;   /*  does the layer mode affect the alpha channel  */
  char *name;         /*  layer mode specification  */
};

LayerMode layer_modes_16[] =
{
  { 1, "Normal" },
  { 1, "Dissolve" },
  { 1, "Behind" },
  { 0, "Multiply" },
  { 0, "Screen" },
  { 0, "Overlay" },
  { 0, "Difference" },
  { 0, "Addition" },
  { 0, "Subtraction" },
  { 0, "Darken Only" },
  { 0, "Lighten Only" },
  { 0, "Hue" },
  { 0, "Saturation" },
  { 0, "Color" },
  { 0, "Value" },
  { 1, "Erase" },
  { 1, "Replace" }
};

/* convolve_area */

typedef void  (*ConvolveAreaFunc) (PixelArea*,PixelArea*,gfloat*,gint, gfloat, gint, gint, gint);
static ConvolveAreaFunc convolve_area_funcs (Tag);
static void convolve_area_u8 (PixelArea*, PixelArea*, gfloat*, gint, gfloat, gint, gint, gint);
static void convolve_area_u16 (PixelArea*, PixelArea*, gfloat*, gint, gfloat, gint, gint, gint);
static void convolve_area_float (PixelArea*, PixelArea*, gfloat*, gint, gfloat, gint, gint, gint);
static void convolve_area_float16 (PixelArea*, PixelArea*, gfloat*, gint, gfloat, gint, gint, gint);

/* gaussian_blur_area */
static void gaussian_blur_area_funcs (Tag);

typedef gdouble*  (*GaussianCurveFunc) (gint,gint*);
static GaussianCurveFunc gaussian_curve;
static gdouble* gaussian_curve_u8 (gint, gint*); 
static gdouble* gaussian_curve_u16 (gint, gint*); 
static gdouble* gaussian_curve_float (gint, gint*); 
static gdouble* gaussian_curve_float16 (gint, gint*); 

typedef void  (*RleRowFunc) (PixelRow*,gint*,PixelRow*);
static RleRowFunc rle_row; 
static void rle_row_u8 (PixelRow*, gint*, PixelRow*);
static void rle_row_u16 (PixelRow*, gint*, PixelRow*);
static void rle_row_float (PixelRow*, gint*, PixelRow*);
static void rle_row_float16 (PixelRow*, gint*, PixelRow*);

typedef void  (*GaussianBlurRowFunc) (PixelRow*,PixelRow*,gint*, PixelRow*,gint,gfloat*,gfloat);
static GaussianBlurRowFunc gaussian_blur_row;
static void gaussian_blur_row_u8 (PixelRow*, PixelRow*, gint*, PixelRow*, gint, gfloat*, gfloat); 
static void gaussian_blur_row_u16 (PixelRow*, PixelRow*, gint*, PixelRow*, gint, gfloat*, gfloat); 
static void gaussian_blur_row_float (PixelRow*, PixelRow*, gint*, PixelRow*, gint, gfloat*, gfloat); 
static void gaussian_blur_row_float16 (PixelRow*, PixelRow*, gint*, PixelRow*, gint, gfloat*, gfloat); 

/* scale_area_no_resample */
static void scale_area_no_resample_funcs (Tag);

typedef void (*ScaleRowNoResampleFunc) (PixelRow*,PixelRow*,gint*);
ScaleRowNoResampleFunc scale_row_no_resample;
static void scale_row_no_resample_u8 (PixelRow*, PixelRow*, gint*);
static void scale_row_no_resample_u16 (PixelRow*, PixelRow*, gint*);
static void scale_row_no_resample_float (PixelRow*, PixelRow*, gint*);
static void scale_row_no_resample_float16 (PixelRow*, PixelRow*, gint*);

/* scale_area */

typedef void (*ScaleRowResampleFunc) (guchar*, guchar*, guchar*, guchar*, 
                                      gdouble, gdouble*, gdouble, gdouble, 
                                      gdouble*, gint, gint, gint, gint); 
static void scale_row_resample_u8    (guchar*, guchar*, guchar*, guchar*, 
                                      gdouble, gdouble*, gdouble, gdouble, 
                                      gdouble*, gint, gint, gint, gint); 
static void scale_row_resample_u16   (guchar*, guchar*, guchar*, guchar*, 
                                      gdouble, gdouble*, gdouble, gdouble, 
                                      gdouble*, gint, gint, gint, gint); 
static void scale_row_resample_float (guchar*, guchar*, guchar*, guchar*, 
                                      gdouble, gdouble*, gdouble, gdouble, 
                                      gdouble*, gint, gint, gint, gint); 
static void scale_row_resample_float16 (guchar*, guchar*, guchar*, guchar*, 
                                      gdouble, gdouble*, gdouble, gdouble, 
                                      gdouble*, gint, gint, gint, gint); 
static ScaleRowResampleFunc scale_row_resample_funcs (Tag);


typedef void (*ScaleSetDestRowFunc)  (PixelArea*, guint, guint, gdouble*);
static void scale_set_dest_row_u8    (PixelArea*, guint, guint, gdouble*);
static void scale_set_dest_row_u16   (PixelArea*, guint, guint, gdouble*);
static void scale_set_dest_row_float (PixelArea*, guint, guint, gdouble*);
static void scale_set_dest_row_float16 (PixelArea*, guint, guint, gdouble*);
static ScaleSetDestRowFunc scale_set_dest_funcs     (Tag);


/* thin_area */
static void thin_area_funcs (Tag); 

typedef gint (*ThinRowFunc) (PixelRow*,PixelRow*,PixelRow*,PixelRow*,gint);
static ThinRowFunc thin_row;
static gint thin_row_u8 (PixelRow*, PixelRow*, PixelRow*, PixelRow*, gint);
static gint thin_row_u16 ( PixelRow*, PixelRow*, PixelRow*, PixelRow*, gint);
static gint thin_row_float ( PixelRow*, PixelRow*, PixelRow*, PixelRow*, gint);
static gint thin_row_float16 ( PixelRow*, PixelRow*, PixelRow*, PixelRow*, gint);


/* gaussian_blur_area */
static gdouble * make_curve                (gdouble, gint*, gdouble);

/* border_area */
static void      draw_segments             (PixelArea*, BoundSeg*,
                                            gint, gint, gint, gfloat );

/* scale_area */
static gdouble   cubic                     (gdouble, gdouble, gdouble, gdouble,
                                            gdouble, gdouble, gdouble);

/* combine_areas */
static int       apply_layer_mode          (PixelRow*, PixelRow*, PixelRow*,
                                            gint, gint, gfloat, gint, gint*);
static int       apply_indexed_layer_mode  (PixelRow*, PixelRow*, PixelRow*,
                                            gint);

/* combine_areas_replace */
static void      apply_layer_mode_replace  (PixelRow*, PixelRow*, PixelRow*,
                                            PixelRow*, gfloat , gint*); 



/*  ColorHash structure  */
typedef struct ColorHash ColorHash;

struct ColorHash
{
  int pixel;           /*  R << 16 | G << 8 | B  */
  int index;           /*  colormap index        */
  int colormap_ID;     /*  colormap ID           */
};

#define MAXDIFF            195076
#define HASH_TABLE_SIZE    1021
static ColorHash color_hash_table [HASH_TABLE_SIZE];
static int color_hash_misses;
static int color_hash_hits;

/* for dissolve mode */
#define RANDOM_TABLE_SIZE  4096
#define RANDOM_SEED        314159265
static int random_table [RANDOM_TABLE_SIZE];


void 
paint_funcs_area_setup  (
                         void
                         )
{
  int i;

  /*  initialize the color hash table--invalidate all entries  */
  for (i = 0; i < HASH_TABLE_SIZE; i++)
    color_hash_table[i].colormap_ID = -1;
  color_hash_misses = 0;
  color_hash_hits = 0;

  /*  generate a table of random seeds  */
#if 0
  srand48 (time (NULL) * RANDOM_SEED);
#endif
  for (i = 0; i < RANDOM_TABLE_SIZE; i++)
    random_table[i] = drand48 ();

  for (i = 0; i < RANDOM_TABLE_SIZE; i++)
    {
      int tmp;
      int swap = i + ((int)(drand48()*RANDOM_SEED)) % (RANDOM_TABLE_SIZE - i);
      tmp = random_table[i];
      random_table[i] = random_table[swap];
      random_table[swap] = tmp;
    }
}


void 
paint_funcs_area_free  (
                        void
                        )
{
  /*  print out the hash table statistics
      d_printf ("RGB->indexed hash table lookups: %d\n", color_hash_hits + color_hash_misses);
      d_printf ("RGB->indexed hash table hits: %d\n", color_hash_hits);
      d_printf ("RGB->indexed hash table misses: %d\n", color_hash_misses);
      d_printf ("RGB->indexed hash table hit rate: %f\n",
      100.0 * color_hash_hits / (color_hash_hits + color_hash_misses));
      */
}



/**************************************************/
/*    AREA FUNCTIONS                              */
/**************************************************/

static int
check_precision (
                 PixelArea * a1,
                 PixelArea * a2,
                 PixelArea * a3
                 )
{
  Tag t1 = pixelarea_tag (a1);

  if (a2)
    if (tag_precision (pixelarea_tag (a2)) != tag_precision (t1))
      return 1;

  if (a3)
    if (tag_precision (pixelarea_tag (a3)) != tag_precision (t1))
      return 1;

  return 0;
}
  
typedef void (*x_addFunc) (PixelRow*, PixelRow*);
static x_addFunc x_add_area_funcs (Tag);
static x_addFunc
x_add_area_funcs (
                   Tag tag
                   )
{
  switch (tag_precision (tag))
    {
    case PRECISION_U8:
      return x_add_row_u8;
    case PRECISION_U16:
      return x_add_row_u16;
    case PRECISION_FLOAT:
      return x_add_row_float;
    case PRECISION_FLOAT16:
      return x_add_row_float16;
    case PRECISION_NONE:
    default:
      g_warning ("x_add: bad precision");
    }
  return NULL;
}

void
x_add_area (
             PixelArea * src_area,
             PixelArea * dest_area 
             )
{
  x_addFunc x_add_row = x_add_area_funcs (pixelarea_tag (src_area));
  void * pag;
  for (pag = pixelarea_register (2, src_area, dest_area);
       pag != NULL;
       pag = pixelarea_process (pag))
    {
      PixelRow r1;
      PixelRow r2;
      gint h = pixelarea_height (src_area);
      while (h--)
        {
          pixelarea_getdata (src_area, &r1, h);
          pixelarea_getdata (dest_area, &r2, h);
          (*x_add_row) (&r1, &r2);
        }
    }
}

typedef void (*x_subFunc) (PixelRow*, PixelRow*);
static x_subFunc x_sub_area_funcs (Tag);
static x_subFunc
x_sub_area_funcs (
                   Tag tag
                   )
{
  switch (tag_precision (tag))
    {
    case PRECISION_U8:
      return x_sub_row_u8;
    case PRECISION_U16:
      return x_sub_row_u16;
    case PRECISION_FLOAT:
      return x_sub_row_float;
    case PRECISION_FLOAT16:
      return x_sub_row_float16;
    case PRECISION_NONE:
    default:
      g_warning ("x_sub: bad precision");
    }
  return NULL;
}

void
x_sub_area (
             PixelArea * src_area,
             PixelArea * dest_area 
             )
{
  x_subFunc x_sub_row = x_sub_area_funcs (pixelarea_tag (src_area));
  void * pag;
  for (pag = pixelarea_register (2, src_area, dest_area);
       pag != NULL;
       pag = pixelarea_process (pag))
    {
      PixelRow r1;
      PixelRow r2;
      gint h = pixelarea_height (src_area);
      while (h--)
        {
          pixelarea_getdata (src_area, &r1, h);
          pixelarea_getdata (dest_area, &r2, h);
          (*x_sub_row) (&r1, &r2);
        }
    }
}

typedef void (*x_minFunc) (PixelRow*, PixelRow*);
static x_minFunc x_min_area_funcs (Tag);
static x_minFunc
x_min_area_funcs (
                   Tag tag
                   )
{
  switch (tag_precision (tag))
    {
    case PRECISION_U8:
      return x_min_row_u8;
    case PRECISION_U16:
      return x_min_row_u16;
    case PRECISION_FLOAT:
      return x_min_row_float;
    case PRECISION_FLOAT16:
      return x_min_row_float16;
    case PRECISION_NONE:
    default:
      g_warning ("x_min: bad precision");
    }
  return NULL;
}

void
x_min_area (
             PixelArea * src_area,
             PixelArea * dest_area 
             )
{
  x_minFunc x_min_row = x_min_area_funcs (pixelarea_tag (src_area));
  void * pag;
  for (pag = pixelarea_register (2, src_area, dest_area);
       pag != NULL;
       pag = pixelarea_process (pag))
    {
      PixelRow r1;
      PixelRow r2;
      gint h = pixelarea_height (src_area);
      while (h--)
        {
          pixelarea_getdata (src_area, &r1, h);
          pixelarea_getdata (dest_area, &r2, h);
          (*x_min_row) (&r1, &r2);
        }
    }
}

typedef void (*invertFunc) (PixelRow*, PixelRow*);
static invertFunc invert_area_funcs (Tag);

static invertFunc
invert_area_funcs (
                   Tag tag
                   )
{
  switch (tag_precision (tag))
    {
    case PRECISION_U8:
      return invert_row_u8;
    case PRECISION_U16:
      return invert_row_u16;
    case PRECISION_FLOAT:
      return invert_row_float;
    case PRECISION_FLOAT16:
      return invert_row_float16;
    case PRECISION_NONE:
    default:
      g_warning ("invert: bad precision");
    }
  return NULL;
}

void
invert_area (
             PixelArea * src_area,
             PixelArea * dest_area 
             )
{
  invertFunc invert_row = invert_area_funcs (pixelarea_tag (src_area));
  void * pag;
  for (pag = pixelarea_register (2, src_area, dest_area);
       pag != NULL;
       pag = pixelarea_process (pag))
    {
      PixelRow r1;
      PixelRow r2;
      gint h = pixelarea_height (src_area);
      while (h--)
        {
          pixelarea_getdata (src_area, &r1, h);
          pixelarea_getdata (dest_area, &r2, h);
          (*invert_row) (&r1, &r2);
        }
    }
}

/* ---------------------------------------------------------*/

typedef void (*AbsDiffFunc) (PixelRow*, PixelRow*, PixelRow*, gfloat, int);
static AbsDiffFunc absdiff_area_funcs (Tag);


static AbsDiffFunc 
absdiff_area_funcs (
                    Tag tag
                    )
	  
{
  switch (tag_precision (tag))
    {
    case PRECISION_U8:
      return absdiff_row_u8;
    case PRECISION_U16:
      return absdiff_row_u16;
    case PRECISION_FLOAT:
      return absdiff_row_float;
    case PRECISION_FLOAT16:
      return absdiff_row_float16;
    case PRECISION_NONE:
    default:
      g_warning ("qq bad precision");
    } 

  return NULL;
}
 
void
absdiff_area (
              PixelArea * image,
              PixelArea * mask,
              PixelRow * color,
              gfloat threshold,
              int antialias
              )
{
  void * pag;
  Tag tag = pixelarea_tag (image); 
  AbsDiffFunc absdiff_row = absdiff_area_funcs (tag);

  if (check_precision (image, mask, NULL))
    {
      g_warning ("aq bad precision");
      return;
    }

  for (pag = pixelarea_register (2, image, mask);
       pag != NULL;
       pag = pixelarea_process (pag))
    {
      PixelRow irow;
      PixelRow mrow;
      gint h = pixelarea_height (image);
      while (h--)
        {
          pixelarea_getdata (image, &irow, h);
          pixelarea_getdata (mask, &mrow, h);
          (*absdiff_row) (&irow, &mrow, color, threshold, antialias);
        }
    }
}

/* extract */
typedef void (*ExtractChannelRowFunc) (PixelRow*, PixelRow*, gint);
static ExtractChannelRowFunc extract_channel_area_funcs (Tag);

static ExtractChannelRowFunc
extract_channel_area_funcs (
                   Tag tag
                   )
{
  switch (tag_precision (tag))
    {
    case PRECISION_U8:
      return extract_channel_row_u8;
    case PRECISION_U16:
      return extract_channel_row_u8;
    case PRECISION_FLOAT:
      return extract_channel_row_float;
    case PRECISION_FLOAT16:
      return extract_channel_row_float16;
    case PRECISION_NONE:
    default:
      g_warning ("extract_channel_area_funcs: bad precision");
    }
  return NULL;
}

void
extract_channel_area (
		       PixelArea * src_area,
		       PixelArea * dest_area, 
		       gint channel
                      )
{
  ExtractChannelRowFunc extract_channel_row =
	 extract_channel_area_funcs (pixelarea_tag (src_area));
  void * pag;
  for (pag = pixelarea_register (2, src_area, dest_area);
       pag != NULL;
       pag = pixelarea_process (pag))
    {
      PixelRow r1;
      PixelRow r2;
      gint h = pixelarea_height (src_area);
      while (h--)
        {
          pixelarea_getdata (src_area, &r1, h);
          pixelarea_getdata (dest_area, &r2, h);
          (*extract_channel_row) (&r1, &r2, channel);
        }
    }
}


/* ---------------------------------------------------------------

                          color area

   the precision specific functions assume that the color has
   an identical tag to the area/row being colored.  they take:

     void * to dest buffer
     void * to color buffer
     width and height of buffer
     pixel and row stride of buffer

*/

typedef void (*ColorRowFunc) (void *, void *, guint, guint, guint, guint);
static ColorRowFunc color_area_funcs (Tag);

static ColorRowFunc 
color_area_funcs (
                  Tag tag
                  )
	  
{
  switch (tag_precision (tag))
  {
  case PRECISION_U8:
    return color_row_u8;
  case PRECISION_U16:
    return color_row_u16;
  case PRECISION_FLOAT:
    return color_row_float;
  case PRECISION_FLOAT16:
    return color_row_float16;
  default:
    return NULL;
  } 
}


void
color_row (
           PixelRow * row,
           PixelRow * col
           )
{
  Tag t = pixelrow_tag (row);
  ColorRowFunc func = color_area_funcs (t);
  PixelRow color;
  guchar color_data[TAG_MAX_BYTES];

  g_return_if_fail (row != NULL);
  g_return_if_fail (col != NULL);
  g_return_if_fail (func != NULL);
  
  if (tag_equal (t, pixelrow_tag (col)) != TRUE)
    {
      pixelrow_init (&color, t, color_data, 1);
      copy_row (col, &color);
      col = &color;
    }

  func ((void *) pixelrow_data (row),
        (void *) pixelrow_data (col),
        pixelrow_width (row),
        1,
        tag_num_channels (t),
        0);
}


void 
color_area  (
             PixelArea * area,
             PixelRow * col
             )
{
  Tag t = pixelarea_tag (area);
  ColorRowFunc func = color_area_funcs (t);
  PixelRow color;
  guchar color_data[TAG_MAX_BYTES];

  g_return_if_fail (area != NULL);
  g_return_if_fail (col != NULL);
  g_return_if_fail (func != NULL);
  
  if (tag_equal (t, pixelrow_tag (col)) != TRUE)
    {
      pixelrow_init (&color, t, color_data, 1);
      copy_row (col, &color);
      col = &color;
    }

  {
    guint pixelstride = tag_num_channels (t);
    void * col_data = (void *) pixelrow_data (col);
    void * pag;
    
    for (pag = pixelarea_register (1, area);
         pag != NULL;
         pag = pixelarea_process (pag))
      {
        func ((void*) pixelarea_data (area),
              col_data,
              pixelarea_width (area),
              pixelarea_height (area),
              pixelstride,
              pixelarea_rowstride (area));
      }
  }
}




/* ---------------------------------------------------------*/

typedef void (*BlendRowFunc) (PixelRow*, PixelRow*, PixelRow*, gfloat, gint);
static BlendRowFunc blend_area_funcs (Tag);

static BlendRowFunc 
blend_area_funcs (
		Tag tag
		)
	  
{
  switch (tag_precision (tag))
  {
  case PRECISION_U8:
    return blend_row_u8;
  case PRECISION_U16:
    return blend_row_u16;
  case PRECISION_FLOAT:
    return blend_row_float;
  case PRECISION_FLOAT16:
    return blend_row_float16;
  default:
    return NULL;
  } 
}

void 
blend_area  (
             PixelArea * src1_area,
             PixelArea * src2_area,
             PixelArea * dest_area,
             gfloat blend,
	     gint alpha
             )
{
  void * pag;
  Tag dest_tag = pixelarea_tag (dest_area); 
  BlendRowFunc blend_row = blend_area_funcs (dest_tag); 
  /* put in tags check */
  
  for (pag = pixelarea_register (3, src1_area, src2_area, dest_area);
       pag != NULL;
       pag = pixelarea_process (pag))
    {
      PixelRow src1_row;
      PixelRow src2_row;
      PixelRow dest_row;
 
      gint h = pixelarea_height (src1_area);
      while (h--)
        {
          pixelarea_getdata (src1_area, &src1_row, h);
          pixelarea_getdata (src2_area, &src2_row, h);
          pixelarea_getdata (dest_area, &dest_row, h);
            
         /*blend_pixels (s1, s2, d, blend, src1->w, src1->bytes, src1_has_alpha); */
           (*blend_row) (&src1_row, &src2_row, &dest_row, blend, alpha);
        }
    }
}

typedef void (*ShadeRowFunc) (PixelRow*, PixelRow*, PixelRow*, gfloat);
static ShadeRowFunc shade_area_funcs (Tag);

static ShadeRowFunc 
shade_area_funcs (
		Tag tag
		)
	  
{
  switch (tag_precision (tag))
  {
  case PRECISION_U8:
    return shade_row_u8;
  case PRECISION_U16:
    return shade_row_u16;
  case PRECISION_FLOAT:
    return shade_row_float;
  case PRECISION_FLOAT16:
    return shade_row_float16;
  default:
    return NULL;
  } 
}

void 
shade_area  (
             PixelArea * src_area,
             PixelArea * dest_area,
             PixelRow * col,
             gfloat blend
             )
{
  void *  pag;
  Tag src_tag = pixelarea_tag (src_area); 
  ShadeRowFunc shade_row = shade_area_funcs (src_tag);
  /*put in tags check*/
  
  for (pag = pixelarea_register (2, src_area, dest_area);
       pag != NULL;
       pag = pixelarea_process (pag))
    {
      PixelRow src_row;
      PixelRow dest_row;
 
      gint h = pixelarea_height (src_area);
      while (h--)
        {
          pixelarea_getdata (src_area, &src_row, h);
          pixelarea_getdata (dest_area, &dest_row, h);
            
          (*shade_row) (&src_row, &dest_row, col, blend);
        }
    }
}



typedef void (*CopyRowFunc) (PixelRow*, PixelRow*);
static CopyRowFunc copy_area_funcs (Tag);

static CopyRowFunc 
copy_area_funcs (
		Tag tag
		)
	  
{
  switch (tag_precision (tag))
  {
  case PRECISION_U8:
    return copy_row_u8;
  case PRECISION_U16:
    return copy_row_u16;
  case PRECISION_FLOAT:
    return copy_row_float;
  case PRECISION_FLOAT16:
    return copy_row_float16;
  default:
    return NULL;
  } 
}

void 
copy_row  (
           PixelRow * src_row,
           PixelRow * dest_row
           )
{
  (*(copy_area_funcs (pixelrow_tag (src_row)))) (src_row, dest_row);
}



void 
copy_area  (
            PixelArea * src_area,
            PixelArea * dest_area
            )
{
  Tag src_tag = pixelarea_tag (src_area);
  PixelRow srow;
  PixelRow drow;
  void * pag;
  CopyRowFunc copyrow = copy_area_funcs (src_tag);

  for (pag = pixelarea_register (2, src_area, dest_area);
       pag != NULL;
       pag = pixelarea_process (pag))
    {
      int h = pixelarea_height (src_area);
      while (h--)
        {
          pixelarea_getdata (src_area, &srow, h);
          pixelarea_getdata (dest_area, &drow, h);
          copyrow (&srow, &drow);
        }
    }
}


typedef void (*AddAlphaRowFunc) (PixelRow*, PixelRow*);
static AddAlphaRowFunc add_alpha_area_funcs (Tag);

static AddAlphaRowFunc 
add_alpha_area_funcs (
		Tag tag
		)
	  
{
  switch (tag_precision (tag))
  {
  case PRECISION_U8:
    return add_alpha_row_u8;
  case PRECISION_U16:
    return add_alpha_row_u16;
  case PRECISION_FLOAT:
    return add_alpha_row_float;
  case PRECISION_FLOAT16:
    return add_alpha_row_float16;
  default:
    return NULL;
  } 
}


void 
add_alpha_area  (
                 PixelArea * src_area,
                 PixelArea * dest_area
                 )
{
  void *  pag;
  Tag src_tag = pixelarea_tag (src_area); 
  AddAlphaRowFunc add_alpha_row = add_alpha_area_funcs (src_tag);

   /*put in tags check*/
  
  for (pag = pixelarea_register (2, src_area, dest_area);
       pag != NULL;
       pag = pixelarea_process (pag))
    {
      PixelRow src_row;
      PixelRow dest_row;
 
      gint h = pixelarea_height (src_area);
      while (h--)
        {
          pixelarea_getdata (src_area, &src_row, h);
          pixelarea_getdata (dest_area, &dest_row, h);
            
  	 /*add_alpha_pixels (s, d, src->w, src->bytes);*/
          (*add_alpha_row) (&src_row, &dest_row);
        }
    }
}

typedef void (*FlattenRowFunc) (PixelRow*, PixelRow*, PixelRow*);
static FlattenRowFunc flatten_area_funcs (Tag);

static FlattenRowFunc 
flatten_area_funcs (
		Tag tag
		)
	  
{
  switch (tag_precision (tag))
  {
  case PRECISION_U8:
    return flatten_row_u8;
  case PRECISION_U16:
    return flatten_row_u16;
  case PRECISION_FLOAT:
    return flatten_row_float;
  case PRECISION_FLOAT16:
    return flatten_row_float16;
  default:
    return NULL;
  } 
}

/* UNUSED */
void 
flatten_area  (
               PixelArea * src_area,
               PixelArea * dest_area,
               PixelRow * bg
               )
{
  void *  pag;
  Tag src_tag = pixelarea_tag (src_area); 
  FlattenRowFunc flatten_row = flatten_area_funcs (src_tag);

   /*put in tags check*/
  
  for (pag = pixelarea_register (2, src_area, dest_area);
       pag != NULL;
       pag = pixelarea_process (pag))
    {
      PixelRow src_row;
      PixelRow dest_row;
 
      gint h = pixelarea_height (src_area);
      while (h--)
        {
          pixelarea_getdata (src_area, &src_row, h);
          pixelarea_getdata (dest_area, &dest_row, h);
            
          /*flatten_pixels (s, d, bg, src->w, src->bytes);*/
          (*flatten_row) (&src_row, &dest_row, bg);
        }
    }
}

typedef void (*ExtractAlphaRowFunc) (PixelRow*, PixelRow*, PixelRow*);
static ExtractAlphaRowFunc extract_alpha_area_funcs (Tag);

static ExtractAlphaRowFunc 
extract_alpha_area_funcs (
		Tag tag
		)
	  
{
  switch (tag_precision (tag))
  {
  case PRECISION_U8:
    return extract_alpha_row_u8;
  case PRECISION_U16:
    return extract_alpha_row_u16;
  case PRECISION_FLOAT:
    return extract_alpha_row_float;
  case PRECISION_FLOAT16:
    return extract_alpha_row_float16;
  default:
    return NULL;
  } 
}

void 
extract_alpha_area  (
                     PixelArea * src_area,
                     PixelArea * mask_area,
                     PixelArea * dest_area
                     )
{
  void *  pag;
  Tag src_tag = pixelarea_tag (src_area); 
  ExtractAlphaRowFunc extract_alpha_row = extract_alpha_area_funcs (src_tag);

   /*put in tags check*/
  
  for (pag = pixelarea_register (3, src_area, dest_area, mask_area);
       pag != NULL;
       pag = pixelarea_process (pag))
    {
      PixelRow src_row;
      PixelRow dest_row;
      PixelRow mask_row;
 
      gint h = pixelarea_height (src_area);
      while (h--)
        {
          pixelarea_getdata (src_area, &src_row, h);
          pixelarea_getdata (dest_area, &dest_row, h);
          pixelarea_getdata (mask_area, &mask_row, h);
            
  	 /* extract_alpha_pixels (s, m, d, src->w, src->bytes); */
          (*extract_alpha_row) (&src_row, &mask_row, &dest_row);
        }
    }
}

typedef void (*ExtractFromIntenRowFunc) (PixelRow*, PixelRow*, PixelRow*, PixelRow*, gint);
typedef void (*ExtractFromIndexedRowFunc) (PixelRow*, PixelRow*, PixelRow*, unsigned char *, PixelRow*, gint);
static ExtractFromIntenRowFunc extract_from_inten_row;
static ExtractFromIndexedRowFunc extract_from_indexed_row;
static void extract_from_area_funcs (Tag);

static void 
extract_from_area_funcs (
			Tag tag
		    )
		  
{
  switch (tag_precision (tag))
  {
  case PRECISION_U8:
    extract_from_inten_row = extract_from_inten_row_u8; 
    extract_from_indexed_row = extract_from_indexed_row_u8; 
    break;
  case PRECISION_U16:
    extract_from_inten_row = extract_from_inten_row_u16; 
    extract_from_indexed_row = extract_from_indexed_row_u16; 
    break;
  case PRECISION_FLOAT:
    extract_from_inten_row = extract_from_inten_row_float; 
    extract_from_indexed_row = NULL; 
    break;
  case PRECISION_FLOAT16:
    extract_from_inten_row = extract_from_inten_row_float16; 
    extract_from_indexed_row = NULL; 
    break;
  default:
    extract_from_inten_row = NULL; 
    extract_from_indexed_row = NULL; 
    break;
  } 
}

void 
extract_from_area  (
                    PixelArea * src_area,
                    PixelArea * dest_area,
                    PixelArea * mask_area,
                    PixelRow * bg,
                    unsigned char * cmap,
                    gint cut
                    )
{
  void * pag;
  Tag src_tag = pixelarea_tag (src_area); 
  Format format = tag_format (src_tag);

  extract_from_area_funcs (src_tag);
   /*put in tags check*/
  
  for (pag = pixelarea_register (3, src_area, dest_area, mask_area);
       pag != NULL;
       pag = pixelarea_process (pag))
    {
      PixelRow src_row;
      PixelRow dest_row;
      PixelRow mask_row;
 
      gint h = pixelarea_height (src_area);
      while (h--)
        {
          pixelarea_getdata (src_area, &src_row, h);
          pixelarea_getdata (dest_area, &dest_row, h);
          pixelarea_getdata (mask_area, &mask_row, h);
            
	  switch (format)
	    {
            case FORMAT_NONE:
              break;
	    case FORMAT_RGB:
	    case FORMAT_GRAY:
  	      /*extract_from_inten_pixels (s, d, m, bg, cut, src->w, src->bytes,src_has_alpha);*/
              (*extract_from_inten_row) (&src_row, &dest_row, &mask_row, bg, cut);
	      break;
	    case FORMAT_INDEXED:
  	      /*extract_from_indexed_pixels (s, d, m, cmap--wheres the cmap?, bg, cut, src->w, src  bytes, src_has_alpha);*/
              (*extract_from_indexed_row) (&src_row, &dest_row, &mask_row, cmap, bg, cut);
	      break;
	    }
        }
    }
}

typedef void (*MultiplyAlphaRowFunc) (PixelRow*);
static MultiplyAlphaRowFunc  multiply_alpha_area_funcs (Tag);

static MultiplyAlphaRowFunc 
multiply_alpha_area_funcs (
		Tag tag
		)
	  
{
  switch (tag_precision (tag))
  {
  case PRECISION_U8:
    return multiply_alpha_row_u8;
  case PRECISION_U16:
    return multiply_alpha_row_u16;
  case PRECISION_FLOAT:
    return multiply_alpha_row_float;
  case PRECISION_FLOAT16:
    return multiply_alpha_row_float16;
  default:
    return NULL;
  } 
}


/* Convert from unmultiplied to premultiplied alpha. */ 
void 
multiply_alpha_area  (
                      PixelArea * src_area
                      )
{
  void *  pag;
  Tag src_tag = pixelarea_tag (src_area); 
  MultiplyAlphaRowFunc multiply_alpha_row = multiply_alpha_area_funcs (src_tag);
 
   /*put in tags check*/
  
  for (pag = pixelarea_register (1, src_area);
       pag != NULL;
       pag = pixelarea_process (pag))
    {
      PixelRow src_row;
 
      gint h = pixelarea_height (src_area);
      while (h--)
        {
          pixelarea_getdata (src_area, &src_row, h);
            
          (*multiply_alpha_row) (&src_row);
        }
    }
}


typedef void (*SeparateAlphaRowFunc) (PixelRow*);
static SeparateAlphaRowFunc separate_alpha_area_funcs (Tag);

static SeparateAlphaRowFunc 
separate_alpha_area_funcs (
		Tag tag
		)
	  
{
  switch (tag_precision (tag))
  {
  case PRECISION_U8:
    return separate_alpha_row_u8;
  case PRECISION_U16:
    return separate_alpha_row_u16;
  case PRECISION_FLOAT:
    return separate_alpha_row_float;
  case PRECISION_FLOAT16:
    return separate_alpha_row_float16;
  default:
    return NULL;
  } 
}


/* Convert from premultiplied alpha to unpremultiplied */
void 
separate_alpha_area  (
                      PixelArea * src_area
                      )
{
  void *  pag;
  Tag src_tag = pixelarea_tag (src_area); 
  SeparateAlphaRowFunc separate_alpha_row = separate_alpha_area_funcs (src_tag);

   /*put in tags check*/
  
  for (pag = pixelarea_register (1, src_area);
       pag != NULL;
       pag = pixelarea_process (pag))
    {
      PixelRow src_row;
 
      gint h = pixelarea_height (src_area);
      while (h--)
        {
          pixelarea_getdata (src_area, &src_row, h);
            
          (*separate_alpha_row) (&src_row);
        }
    }
}


static ConvolveAreaFunc 
convolve_area_funcs (
			Tag tag
		    )
		  
{
  switch (tag_precision (tag))
  {
  case PRECISION_U8:
    return convolve_area_u8; 
  case PRECISION_U16:
    return convolve_area_u16; 
  case PRECISION_FLOAT:
    return convolve_area_float; 
  case PRECISION_FLOAT16:
    return convolve_area_float16; 
  default:
    return NULL; 
  } 
}

void 
convolve_area  (
                PixelArea * src_area,
                PixelArea * dest_area,
                gfloat * matrix,
                guint matrix_size,
                gfloat divisor,
                gint mode,
		gint alpha
                )
{
  void * pag;
  gint offset;
  guint src_area_height = pixelarea_areaheight (src_area);  
  guint src_area_width = pixelarea_areawidth (src_area);  
  Tag src_tag = pixelarea_tag (src_area);
  Tag dest_tag = pixelarea_tag (dest_area);
  ConvolveAreaFunc convolve_area_func = convolve_area_funcs (dest_tag);

  if ( !tag_equal (src_tag, dest_tag) )
     return;

  /*  If the mode is NEGATIVE, the offset should be 128  */
  if (mode == NEGATIVE)
    {
      offset = 128;
      mode = 0;
    }
  else
    offset = 0;
  
  /*  check for the boundary cases  */
  if (src_area_width < (matrix_size - 1) || 
      src_area_height < (matrix_size - 1))
    return;

  /* the processing loop is needed so the pixelareas will be
     reffed properly, even though both buffers are flat */
  for (pag = pixelarea_register (2, src_area, dest_area);
       pag != NULL;
       pag = pixelarea_process (pag))
    {
      (*convolve_area_func) (src_area, dest_area, matrix, 
                             matrix_size, divisor, mode, offset, alpha);
    }
}

static void
convolve_area_u8 (
		  PixelArea   *src_area,
		  PixelArea   *dest_area,
		  gfloat         *matrix,
		  gint          size,
		  gfloat        divisor,
		  gint          mode,
                  gint          offset,
		  gint		alpha
		  )
{
  guint8 *src, * s;
  guint8 *dest;
  gfloat * m;
  gfloat total [4];
  gint b, num_channels;
  gint margin;      /*  margin imposed by size of conv. matrix  */
  gint i, j;
  gint x, y;
  gint src_rowstride;
  gint dest_rowstride;
  gint src_height;
  gint src_width;
  gint bytes;
  Tag src_tag = pixelarea_tag (src_area); 
  
  /*  Initialize some values  */
  src_width = pixelarea_areawidth (src_area);
  src_height = pixelarea_areaheight (src_area);
  bytes = tag_bytes (src_tag);  /* per pixel */ 
  num_channels = tag_num_channels (src_tag); /* per pixel */
  
  src_rowstride =  pixelarea_rowstride (src_area);
  dest_rowstride = pixelarea_rowstride (dest_area);

  margin = size / 2;
  src = (guint8 *) pixelarea_data (src_area);
  dest = (guint8 *) pixelarea_data (dest_area);
  
  /* copy the first (size / 2) scanlines of the src image... */
  for (i = 0; i < margin; i++)
    {
      memcpy (dest, src, src_width * bytes);
      src += src_rowstride;
      dest += dest_rowstride;
    }

  /* then do the middle  scanlines*/ 
  for (y = margin; y < src_height - margin; y++)
    {
      /* first handle the left margin pixels */
      b = num_channels * margin;
      while (b --)
	*dest++ = *src++;
     
      /* now, handle the center pixels */
      x = src_width - margin*2;
      while (x--)
	{
	  /* go to the beginning of the src under the kernal */
	  s = src - margin * (num_channels + src_rowstride);

	  m = matrix;
	  total [0] = total [1] = total [2] = total [3] = 0;
	  i = size;
	  while (i --)
	    {
	      j = size;
	      while (j --)
		{
		  for (b = 0; b < num_channels; b++)
		    total [b] += *m * *s++;
		  m ++;
		}

	      /* go to the next line of the src under the kernal */
	      s += src_rowstride - size * num_channels;
	    }

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

	      /*  only if mode was ABSOLUTE will mode by non-zero here  */
	      if (total [b] < 0 && mode)
		total [b] = - total [b];

	      if (total [b] < 0)
		*dest++ = 0;
	      else
		*dest++ = (total [b] > 255) ? 255 : (guint8) total [b];
	    }
	  src += num_channels;
	}
      
      /* now the right margin pixels*/
      b = num_channels * margin;
      while (b --)
	*dest++ = *src++;
    }

  /* copy the last (margin) scanlines of the src image... */
  for (i = 0; i < margin; i++)
    {
      memcpy (dest, src, src_width * bytes) ;
      src += src_rowstride;
      dest += dest_rowstride;
    }
}

static void
convolve_area_u16 (
		   PixelArea   *src_area,
		   PixelArea   *dest_area,
		   gfloat       *matrix,
		   gint          size,
		   gfloat        divisor,
		   gint          mode,
                   gint          offset,
		   gint		alpha
		   )
{
  guint16 *src, * s;
  guint16 *dest;
  gfloat * m;
  gfloat total [4];
  gint b, num_channels;
  gint margin;      /*  margin imposed by size of conv. matrix  */
  gint i, j;
  gint x, y;
  gint src_rowstride;
  gint dest_rowstride;
  gint src_height;
  gint src_width;
  gint bytes;
  Tag src_tag = pixelarea_tag (src_area); 
  
  /*  Initialize some values  */
  src_width = pixelarea_areawidth (src_area);
  src_height = pixelarea_areaheight (src_area);
  bytes = tag_bytes (src_tag);  /* per pixel */ 
  num_channels = tag_num_channels (src_tag); /* per pixel */
 
  src_rowstride =  pixelarea_rowstride (src_area) / sizeof (guint16);
  dest_rowstride = pixelarea_rowstride (dest_area) / sizeof (guint16);

  margin = size / 2;
  src = (guint16 *) pixelarea_data (src_area);
  dest = (guint16 *) pixelarea_data (dest_area);
  
  /* copy the first (size / 2) scanlines of the src image... */
  for (i = 0; i < margin; i++)
    {
      memcpy (dest, src, src_width * bytes);
      src += src_rowstride;
      dest += dest_rowstride;
    }

  /* then do the middle  scanlines*/ 
  for (y = margin; y < src_height - margin; y++)
    {
      /* first handle the left margin pixels */
      b = num_channels * margin;
      while (b --)
	*dest++ = *src++;
     
      /* now, handle the center pixels */
      x = src_width - margin*2;
      while (x--)
	{
	  /* go to the beginning of the src under the kernal */
	  s = src - margin * (num_channels + src_rowstride);

	  m = matrix;
	  total [0] = total [1] = total [2] = total [3] = 0;
	  i = size;
	  while (i --)
	    {
	      j = size;
	      while (j --)
		{
		  for (b = 0; b < num_channels; b++)
		    total [b] += *m * *s++;
		  m ++;
		}

	      /* go to the next line of the src under the kernal */
	      s += src_rowstride - size * num_channels;
	    }

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

	      /*  only if mode was ABSOLUTE will mode by non-zero here  */
	      if (total [b] < 0 && mode)
		total [b] = - total [b];

	      if (total [b] < 0)
		*dest++ = 0;
	      else
		*dest++ = (total [b] > 65535) ? 65535 : (guint16) total [b];
	    }
	  src += num_channels;
	}
      
      /* now the right margin pixels*/
      b = num_channels * margin;
      while (b --)
	*dest++ = *src++;
    }

  /* copy the last (margin) scanlines of the src image... */
  for (i = 0; i < margin; i++)
    {
      memcpy (dest, src, src_width * bytes) ;
      src += src_rowstride;
      dest += dest_rowstride;
    }
}

static void
convolve_area_float (PixelArea *src_area,
		 PixelArea    *dest_area,
		 gfloat        *matrix,
		 gint          size,
		 gfloat        divisor,
		 gint          mode,
                 gint          offset,
		 gint		alpha)
{
  gfloat *src, * s;
  gfloat *dest;
  gfloat * m;
  gfloat total [4];
  gint b, num_channels;
  gint margin;      /*  margin imposed by size of conv. matrix  */
  gint i, j;
  gint x, y;
  gint src_rowstride;
  gint dest_rowstride;
  gint src_height;
  gint src_width;
  gint bytes;
  Tag src_tag = pixelarea_tag (src_area); 
  
  /*  Initialize some values  */
  src_width = pixelarea_areawidth (src_area);
  src_height = pixelarea_areaheight (src_area);
  bytes = tag_bytes (src_tag);  /* per pixel */ 
  num_channels = tag_num_channels (src_tag); /* per pixel */
 
  src_rowstride =  pixelarea_rowstride (src_area) / sizeof (gfloat);
  dest_rowstride = pixelarea_rowstride (dest_area) / sizeof (gfloat);

  margin = size / 2;
  src = (gfloat *) pixelarea_data (src_area);
  dest = (gfloat *) pixelarea_data (dest_area);
  
  /* copy the first (size / 2) scanlines of the src image... */
  for (i = 0; i < margin; i++)
    {
      memcpy (dest, src, src_width * bytes);
      src += src_rowstride;
      dest += dest_rowstride;
    }

  /* then do the middle  scanlines*/ 
  for (y = margin; y < src_height - margin; y++)
    {
      /* first handle the left margin pixels */
      b = num_channels * margin;
      while (b --)
	*dest++ = *src++;
     
      /* now, handle the center pixels */
      x = src_width - margin*2;
      while (x--)
	{
	  /* go to the beginning of the src under the kernal */
	  s = src - margin * (num_channels + src_rowstride);

	  m = matrix;
	  total [0] = total [1] = total [2] = total [3] = 0;
	  i = size;
	  while (i --)
	    {
	      j = size;
	      while (j --)
		{
		  for (b = 0; b < num_channels; b++)
		    total [b] += *m * *s++;
		  m ++;
		}

	      /* go to the next line of the src under the kernal */
	      s += src_rowstride - size * num_channels;
	    }

	  for (b = 0; b < num_channels; b++)
	    {
	      total [b] = total [b] / divisor + offset;
               /*  only if mode was ABSOLUTE will mode by non-zero here  */
	      if (total [b] < 0 && mode)
		total [b] = - total [b];

		*dest++ = total [b];
	    }
	  src += num_channels;
	}
      
      /* now the right margin pixels*/
      b = num_channels * margin;
      while (b --)
	*dest++ = *src++;
    }

  /* copy the last (margin) scanlines of the src image... */
  for (i = 0; i < margin; i++)
    {
      memcpy (dest, src, src_width * bytes) ;
      src += src_rowstride;
      dest += dest_rowstride;
    }
}

static void
convolve_area_float16 (PixelArea *src_area,
		 PixelArea    *dest_area,
		 gfloat        *matrix,
		 gint          size,
		 gfloat        divisor,
		 gint          mode,
                 gint          offset,
		 gint 		alpha)
{
  guint16 *src, * s;
  guint16 *dest;
  gfloat * m, a;
  gfloat total [4], atotal;
  gint b, num_channels;
  gint margin;      /*  margin imposed by size of conv. matrix  */
  gint i, j;
  gint x, y;
  gint src_rowstride;
  gint dest_rowstride;
  gint src_height;
  gint src_width;
  gint bytes;
  Tag src_tag = pixelarea_tag (src_area); 
  gfloat val;
  ShortsFloat u;
  gint a_index = tag_alpha_index (src_tag);

  /*  Initialize some values  */
  src_width = pixelarea_areawidth (src_area);
  src_height = pixelarea_areaheight (src_area);
  bytes = tag_bytes (src_tag);  /* per pixel */ 
  num_channels = tag_num_channels (src_tag); /* per pixel */
 
  src_rowstride =  pixelarea_rowstride (src_area) / sizeof (guint16);
  dest_rowstride = pixelarea_rowstride (dest_area) / sizeof (guint16);

  margin = size / 2;
  src = (guint16 *) pixelarea_data (src_area);
  dest = (guint16 *) pixelarea_data (dest_area);
  
  /* copy the first (size / 2) scanlines of the src image... */
  for (i = 0; i < margin; i++)
    {
      memcpy (dest, src, src_width * bytes);
      src += src_rowstride;
      dest += dest_rowstride;
    }

  /* then do the middle  scanlines*/ 
  for (y = margin; y < src_height - margin; y++)
    {
      /* first handle the left margin pixels */
      b = num_channels * margin;
      while (b --)
	*dest++ = *src++;
     
      /* now, handle the center pixels */
      x = src_width - margin*2;
      while (x--)
	{
	  /* go to the beginning of the src under the kernal */
	  s = src - margin * (num_channels + src_rowstride);

	  m = matrix;
	  total [0] = total [1] = total [2] = total [3] = 0;
	  atotal = 0;
	  i = size;  /* row */
	  while (i --)
	    {
	      j = size;
	      while (j --) /* column */
		{
	      	  atotal += a = FLT (s[a_index], u);
		  for (b = 0; b < num_channels; b++)
			{
			  val = FLT (*s++, u);
		          total [b] += b != a_index && alpha ?  (*m) * val * a : (*m) * val * a;
			}
		  m ++;
		}

	      /* go to the next line of the src under the kernal */
	      s += src_rowstride - size * num_channels;
	    }

	  for (b = 0; b < num_channels; b++)
	    {
	      total [b] = b != a_index && alpha ? 
		      (total[a_index] ? total [b]  / (total [a_index]) + offset : 0) :
		      total [b] / divisor + offset;
               /*  only if mode was ABSOLUTE will mode by non-zero here  */
	      if (total [b] < 0 && mode)
		total [b] = - total [b];

		*dest++ = FLT16 (total [b], u);
	    }
	  src += num_channels;
	}
      
      /* now the right margin pixels*/
      b = num_channels * margin;
      while (b --)
	*dest++ = *src++;
    }

  /* copy the last (margin) scanlines of the src image... */
  for (i = 0; i < margin; i++)
    {
      memcpy (dest, src, src_width * bytes) ;
      src += src_rowstride;
      dest += dest_rowstride;
    }
}

/**************************************************/
/*    GAUSSIAN_BLUR                               */
/**************************************************/

static void 
gaussian_blur_area_funcs (
			Tag tag
		    )
		  
{
  switch (tag_precision (tag))
  {
  case PRECISION_U8:
    gaussian_curve = gaussian_curve_u8;	
    gaussian_blur_row = gaussian_blur_row_u8;	
    rle_row = rle_row_u8; 
    break;
  case PRECISION_U16:
    gaussian_curve = gaussian_curve_u16;	
    gaussian_blur_row = gaussian_blur_row_u16;	
    rle_row = rle_row_u16; 
    break;
  case PRECISION_FLOAT:
    gaussian_curve = gaussian_curve_float;	
    gaussian_blur_row = gaussian_blur_row_float;	
    rle_row = rle_row_float; 
    break;
  case PRECISION_FLOAT16:
    gaussian_curve = gaussian_curve_float16;	
    gaussian_blur_row = gaussian_blur_row_float16;	
    rle_row = rle_row_float16; 
    break;
  default:
    gaussian_curve = NULL;	
    gaussian_blur_row = NULL;	
    rle_row = NULL; 
    break;
  } 
}

void 
gaussian_blur_area  (
                     PixelArea * src_area,
                     gdouble radius
                     )
{
  gdouble *curve;
  gfloat *sum, total;
  gint length;
  guint i, col, row;
  PixelRow src_row, src_col, dest_row, rle_values;
  guchar *src_row_data, *src_col_data, *dest_row_data, *rle_values_data;
  gint *rle_count;  
  Tag rle_values_tag;
  Tag src_tag = pixelarea_tag (src_area);
  guint width = pixelarea_areawidth (src_area);
  guint height = pixelarea_areaheight (src_area);
  guint bytes_per_pixel = tag_bytes (src_tag);
  guint num_channels = tag_num_channels (src_tag);  /*per pixel*/
  guint bytes_per_channel = bytes_per_pixel / num_channels;
  gint src_x = pixelarea_x (src_area);
  gint src_y = pixelarea_y (src_area);
  /* add a tag check here to look for an alpha channel */

  gaussian_blur_area_funcs (src_tag);
 
  if (radius == 0.0) return;		
  
  /* get a gaussian  */
  curve = (*gaussian_curve)( 2 * radius + 1, &length ); 

  /* compute a sum for the gaussian */ 
  sum = g_malloc (sizeof (gfloat) * (2 * length + 1));
  sum[0] = 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];

  /* create src column and row pixel rows */
  src_col_data = (guchar *) g_malloc (height * bytes_per_pixel);
  src_row_data = (guchar *) g_malloc (width * bytes_per_pixel);
  dest_row_data = (guchar *) g_malloc (width * bytes_per_pixel); 
  pixelrow_init (&src_col, src_tag, src_col_data, height);
  pixelrow_init (&src_row, src_tag, src_row_data, width);
  pixelrow_init (&dest_row, src_tag, dest_row_data, width);

  /* create rle arrays */
  rle_count = g_malloc (sizeof (int) * MAXIMUM (width, height)); 
  
  rle_values_tag = tag_new (tag_precision (src_tag), FORMAT_GRAY, ALPHA_NO); 
  rle_values_data = (guchar *) g_malloc (MAXIMUM (width, height) * bytes_per_channel); 
  pixelrow_init (&rle_values, rle_values_tag , rle_values_data, MAXIMUM(width, height));
 
  /* first do the columns */
  for (col = 0; col < width; col++)
    {
      /* get a copy of the column from src */
      /*pixel_region_get_col (srcR, col + srcR->x, srcR->y, height, src, 1);*/
      pixelarea_copy_col (src_area, &src_col, src_x + col, src_y, height, 1);
      
      /* get an "rle" of the columns alpha */
      (*rle_row) (&src_col, rle_count, &rle_values);
      
      /* run a 1d gaussian on the column */
      (*gaussian_blur_row) (&src_col, &src_col, rle_count, 
					 &rle_values, length, sum, total); 

      /* copy the result back into the src */
      /*pixel_region_set_col (srcR, col + srcR->x, srcR->y, height, src);*/
      pixelarea_write_col (src_area, &src_col, src_x + col, src_y, height);
    }

  /* do the rows next */
  for (row = 0; row < height; row++)
    {
      /*pixel_region_get_row (srcR, srcR->x, row + srcR->y, width, src, 1);*/
      
      /* make copies of the row for src and dest */
 
      pixelarea_copy_row (src_area, &src_row, src_x, src_y + row, width, 1);
      pixelarea_copy_row (src_area, &dest_row, src_x, src_y + row, width, 1); 
      
      /* get an "rle" of the rows alpha */
      (*rle_row) (&src_row, rle_count, &rle_values);
      
      /* run a 1d gaussian on the row */
      (*gaussian_blur_row) (&src_row, &dest_row, rle_count, 
					 &rle_values, length, sum, total); 

      /* copy the result into src */ 	
      /*pixel_region_set_row (srcR, srcR->x, row + srcR->y, width, dest);*/
      pixelarea_write_row (src_area, &dest_row, src_x, src_y + row, width);
    }
    g_free (sum - length);
    g_free (curve - length);
    g_free (rle_values_data);
    g_free (src_row_data);
    g_free (src_col_data);
    g_free (dest_row_data);
}

static gdouble * 
gaussian_curve_u8  (
                    gint radius,
                    gint * length
                    )
{
  gdouble std_dev;
  std_dev = sqrt (-(radius * radius) / (2 * log (1.0/255.0)));
  return make_curve (std_dev, length, 1.0/255.0);
}

static gdouble * 
gaussian_curve_u16  (
                     gint radius,
                     gint * length
                     )
{
  gdouble std_dev;
  std_dev = sqrt (-(radius * radius) / (2 * log (1.0/65535.0)));
  return make_curve (std_dev, length, 1.0/65535.0);
}

static gdouble * 
gaussian_curve_float  (
                       gint radius,
                       gint * length
                       )
{
  gdouble std_dev;
  std_dev = sqrt (-(radius * radius) / (2 * log (.000001)));
  return make_curve (std_dev, length, .000001 );
}

static gdouble * 
gaussian_curve_float16  (
                       gint radius,
                       gint * length
                       )
{
  gdouble std_dev;
  std_dev = sqrt (-(radius * radius) / (2. * log (.000001)));
  return make_curve (std_dev, length, .000001 );
}

static void 
rle_row_u8  (
             PixelRow * src_row    /* in  -- encode this row */,
             gint * how_many       /* out -- how many of each code */,
             PixelRow * values_row /* out -- the codes */
             )
{
  gint 	  start;
  gint    i;
  gint    j;
  gint    pixels_equal;
  gint    b;
  gint    num_channels = tag_num_channels (pixelrow_tag (src_row));
  gint    width        = pixelrow_width (src_row);
  guint8 *src          = (guint8*)pixelrow_data (src_row);
  guint8 *values       = (guint8*)pixelrow_data (values_row);
  guint8 *last;
  
  last = src;
  src += num_channels;
  start = 0;
  for (i = 1; i < width; i++)
    {
      b = num_channels;
      pixels_equal = TRUE;
      for( b = 0; b < num_channels; b++)	
	{
	  if( src[b] != last[b] ) 
	  { 
	    pixels_equal = FALSE;
	    break;
	  } 
	}
      if (!pixels_equal)
	{
	  for (j = start; j < i; j++)
	    {
	      *how_many++ = (i - j);
               for( b = 0; b < num_channels; b++)	
	         values[b]  = last[b];
               values += num_channels;
	    }
	  start = i;
	  last = src;
	}
      src += num_channels;
    }

  for (j = start; j < i; j++)
    {
      *how_many++ = (i - j);
       for( b = 0; b < num_channels; b++)	
	 values[b]  = last[b];
       values += num_channels;
    }
}

static void 
rle_row_u16  (
              PixelRow * src_row     /* in  -- encode this row */,
              gint * how_many        /* out -- how many of each code */,
              PixelRow * values_row  /* out -- the codes */
              )
{
  gint start;
  gint i;
  gint j;
  gint pixels_equal;
  gint b;
  gint num_channels = tag_num_channels (pixelrow_tag (src_row));
  gint    width        = pixelrow_width (src_row);
  guint16 *src = (guint16*)pixelrow_data (src_row);
  guint16 *values = (guint16*)pixelrow_data (values_row);
  guint16 *last;
  
  last = src;
  src += num_channels;
  start = 0;
  for (i = 1; i < width; i++)
    {
      b = num_channels;
      pixels_equal = TRUE;
      for( b = 0; b < num_channels; b++)	
	{
	  if( src[b] != last[b] ) 
	  { 
	    pixels_equal = FALSE;
	    break;
	  } 
	}
      if (!pixels_equal)
	{
	  for (j = start; j < i; j++)
	    {
	      *how_many++ = (i - j);
               for( b = 0; b < num_channels; b++)	
	          values[b]  = last[b];
               values += num_channels;
	    }
	  start = i;
	  last = src;
	}
      src += num_channels;
    }

  for (j = start; j < i; j++)
    {
      *how_many++ = (i - j);
      for( b = 0; b < num_channels; b++)	
	values[b]  = last[b];
      values += num_channels;
    }
}

static void 
rle_row_float  (
                PixelRow * src_row     /* in  -- encode this row */,
                gint * how_many        /* out -- how many of each code */,
                PixelRow * values_row  /* out -- the codes */
                )
{
  int start;
  int i;
  int j;
  int pixels_equal;
  int b;
  int num_channels = tag_num_channels (pixelrow_tag (src_row));
  gint    width        = pixelrow_width (src_row);
  gfloat *src = (gfloat*)pixelrow_data (src_row);
  gfloat *values = (gfloat*)pixelrow_data (values_row);
  gfloat *last;
  
  last = src;
  src += num_channels;
  start = 0;
  for (i = 1; i < width; i++)
    {
      b = num_channels;
      pixels_equal = TRUE;
      for( b = 0; b < num_channels; b++)	
	{
	  if( src[b] != last[b] ) 
	  { 
	    pixels_equal = FALSE;
	    break;
	  } 
	}
      
	if (!pixels_equal)
	{
	  for (j = start; j < i; j++)
	    {
	      *how_many++ = (i - j);
               for( b = 0; b < num_channels; b++)	
	          values[b]  = last[b];
               values += num_channels;
	    }
	  start = i;
	  last = src;
	}
      src += num_channels;
    }

  for (j = start; j < i; j++)
    {
      *how_many++ = (i - j);
      for( b = 0; b < num_channels; b++)	
	values[b]  = last[b];
      values += num_channels;
    }
}

static void 
rle_row_float16  (
                PixelRow * src_row     /* in  -- encode this row */,
                gint * how_many        /* out -- how many of each code */,
                PixelRow * values_row  /* out -- the codes */
                )
{
  int start;
  int i;
  int j;
  int pixels_equal;
  int b;
  int num_channels = tag_num_channels (pixelrow_tag (src_row));
  gint    width        = pixelrow_width (src_row);
  guint16 *src = (guint16*)pixelrow_data (src_row);
  guint16 *values = (guint16*)pixelrow_data (values_row);
  guint16 *last;
  
  last = src;
  src += num_channels;
  start = 0;
  for (i = 1; i < width; i++)
    {
      b = num_channels;
      pixels_equal = TRUE;
      for( b = 0; b < num_channels; b++)	
	{
	  if( src[b] != last[b] ) 
	  { 
	    pixels_equal = FALSE;
	    break;
	  } 
	}
      
	if (!pixels_equal)
	{
	  for (j = start; j < i; j++)
	    {
	      *how_many++ = (i - j);
               for( b = 0; b < num_channels; b++)	
	          values[b]  = last[b];
               values += num_channels;
	    }
	  start = i;
	  last = src;
	}
      src += num_channels;
    }

  for (j = start; j < i; j++)
    {
      *how_many++ = (i - j);
      for( b = 0; b < num_channels; b++)	
	values[b]  = last[b];
      values += num_channels;
    }
}

static void 
gaussian_blur_row_u8  (
                       PixelRow * src,
                       PixelRow * dest,
                       gint * rle_count,
                       PixelRow * rle_values,
                       gint length,
                       gfloat * sum,
                       gfloat total
                       )
{  
  gint val, x, i, start, end, pixels;
  guint8 *rle_values_ptr;
  gint   *rle_count_ptr;
  guint8 *src_data = (guint8*)pixelrow_data (src);
  guint8 *dest_data = (guint8*)pixelrow_data (dest);
  guint8 *rle_values_data = (guint8*)pixelrow_data (rle_values);
  Tag src_tag = pixelrow_tag (src);
  gint num_channels = tag_num_channels (src_tag);
  gint width = pixelrow_width (src);
  gint alpha = num_channels - 1;  /*same for both */
  guint8 *sp = src_data + alpha;
  guint8 *dp = dest_data + alpha;
  gint initial_p = sp[0];
  gint initial_m = sp[(width -1) * num_channels];
  for (x = 0; x < width; x++)
    {
      start = (x < length) ? -x : -length;
      end = (width <= (x + length)) ? (width - x - 1) : length;
      val = 0;
      i = start;
      rle_count_ptr = rle_count + (x + i);
      rle_values_ptr = (guint8*)rle_values_data + (x+i); 

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

      while (i < end)
	{
	  pixels = *rle_count_ptr;
	  i += pixels;
	  if (i > end)
	    i = end;
	  val += *rle_values_ptr * (sum[i] - sum[start]);
	  rle_values_ptr += pixels;
	  rle_count_ptr += pixels;
	  start = i;
	}

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

      dp[x * num_channels] = val / total;
    }
}

static void 
gaussian_blur_row_u16  (
                        PixelRow * src,
                        PixelRow * dest,
                        gint * rle_count,
                        PixelRow * rle_values,
                        gint length,
                        gfloat * sum,
                        gfloat total
                        )
{  
  gint val, x, i, start, end, pixels;
  guint16 *rle_values_ptr;
  gint   *rle_count_ptr;
  guint16 *src_data = (guint16*)pixelrow_data (src);
  guint16 *dest_data = (guint16*)pixelrow_data (dest);
  guint16 *rle_values_data = (guint16*)pixelrow_data (rle_values);
  Tag src_tag = pixelrow_tag (src);
  gint num_channels = tag_num_channels (src_tag);
  gint width = pixelrow_width (src);
  gint alpha = num_channels - 1;  /*same for both */
  guint16 *sp = src_data + alpha;
  guint16 *dp = dest_data + alpha;
  gint initial_p = sp[0];
  gint initial_m = sp[(width -1) * num_channels];
  
  for (x = 0; x < width; x++)
    {
      start = (x < length) ? -x : -length;
      end = (width <= (x + length)) ? (width - x - 1) : length;
      val = 0;
      i = start;
      rle_count_ptr = rle_count + (x + i);
      rle_values_ptr = (guint16*)rle_values_data + (x+i); 

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

      while (i < end)
	{
	  pixels = *rle_count_ptr;
	  i += pixels;
	  if (i > end)
	    i = end;
	  val += *rle_values_ptr * (sum[i] - sum[start]);
	  rle_values_ptr += pixels;
	  rle_count_ptr += pixels;
	  start = i;
	}

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

      dp[x * num_channels] = val / total;
    }
}

static void 
gaussian_blur_row_float  (
                          PixelRow * src,
                          PixelRow * dest,
                          gint * rle_count,
                          PixelRow * rle_values,
                          gint length,
                          gfloat * sum,
                          gfloat total
                          )
{  
  gfloat val; 
  gint x, i, start, end, pixels;
  gfloat *rle_values_ptr;
  gint   *rle_count_ptr;
  gfloat *src_data = (gfloat *)pixelrow_data (src);
  gfloat *dest_data = (gfloat *)pixelrow_data (dest);
  gfloat *rle_values_data = (gfloat*)pixelrow_data (rle_values);
  Tag src_tag = pixelrow_tag (src);
  gint num_channels = tag_num_channels (src_tag);
  gint width = pixelrow_width (src);
  gint alpha = num_channels - 1;  /*same for both */
  gfloat *sp = src_data + alpha;
  gfloat *dp = dest_data + alpha;
  gfloat initial_p = sp[0];
  gfloat initial_m = sp[(width -1) * num_channels];
  
  for (x = 0; x < width; x++)
    {
      start = (x < length) ? -x : -length;
      end = (width <= (x + length)) ? (width - x - 1) : length;
      val = 0;
      i = start;
      rle_count_ptr = rle_count + (x + i);
      rle_values_ptr = (gfloat*)rle_values_data + (x+i); 

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

      while (i < end)
	{
	  pixels = *rle_count_ptr;
	  i += pixels;
	  if (i > end)
	    i = end;
	  val += *rle_values_ptr * (sum[i] - sum[start]);
	  rle_values_ptr += pixels;
	  rle_count_ptr += pixels;
	  start = i;
	}

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

      dp[x * num_channels] = val / total;
    }
}

static void 
gaussian_blur_row_float16  (
                          PixelRow * src,
                          PixelRow * dest,
                          gint * rle_count,
                          PixelRow * rle_values,
                          gint length,
                          gfloat * sum,
                          gfloat total
                          )
{  
  gfloat val; 
  gint    x, i, start, end, pixels;
  guint16 *rle_values_ptr;
  gint   *rle_count_ptr;
  guint16 *src_data = (guint16 *)pixelrow_data (src);
  guint16 *dest_data = (guint16 *)pixelrow_data (dest);
  guint16 *rle_values_data = (guint16*)pixelrow_data (rle_values);
  Tag src_tag = pixelrow_tag (src);
  gint num_channels = tag_num_channels (src_tag);
  gint width = pixelrow_width (src);
  gint alpha = num_channels - 1;  /*same for both */
  guint16 *sp = src_data + alpha;
  guint16 *dp = dest_data + alpha;
  ShortsFloat u;
  gfloat initial_p = FLT (sp[0], u);
  gfloat initial_m = FLT (sp[(width -1) * num_channels], u);
  
  for (x = 0; x < width; x++)
    {
      start = (x < length) ? -x : -length;
      end = (width <= (x + length)) ? (width - x - 1) : length;
      val = 0;
      i = start;
      rle_count_ptr = rle_count + (x + i);
      rle_values_ptr = (guint16*)rle_values_data + (x+i); 

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

      while (i < end)
	{
	  pixels = *rle_count_ptr;
	  i += pixels;
	  if (i > end)
	    i = end;
	  val += FLT (*rle_values_ptr, u) * (sum[i] - sum[start]);
	  rle_values_ptr += pixels;
	  rle_count_ptr += pixels;
	  start = i;
	}

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

      dp[x * num_channels] = FLT16 (val / total, u);
    }
}


void 
border_area  (
              PixelArea * dest_area,
              void * bs_ptr,
              gint bs_segs,
              gint radius
              )
{
  BoundSeg * bs;
  gint r, i, j;
  gfloat opacity;
  
  /* should do a check on the dest_tag to make sure grayscale */
 
  bs = (BoundSeg *) bs_ptr;

  /*  draw the border  */
  for (r = 0; r <= radius; r++)
    {
      opacity = (gfloat) (r + 1) / (gfloat) (radius + 1);

      j = radius - r;

      for (i = 0; i <= j; i++)
	{
	  /*  northwest  */
	  draw_segments (dest_area, bs, bs_segs, -i, -(j - i), opacity);

	  /*  only draw the rest if they are different  */
	  if (j)
	    {
	      /*  northeast  */
	      draw_segments (dest_area, bs, bs_segs, i, -(j - i), opacity);
	      /*  southeast  */
	      draw_segments (dest_area, bs, bs_segs, i, (j - i), opacity);
	      /*  southwest  */
	      draw_segments (dest_area, bs, bs_segs, -i, (j - i), opacity);
	    }
	}
    }
}


/* non-interpolating scale_region.  [adam]
 */

static void
scale_area_no_resample_funcs (Tag tag)
{
  switch (tag_precision (tag))
  {
  case PRECISION_U8:
    scale_row_no_resample = scale_row_no_resample_u8;	
    break;
  case PRECISION_U16:
    scale_row_no_resample = scale_row_no_resample_u16;	
    break;
  case PRECISION_FLOAT:
    scale_row_no_resample = scale_row_no_resample_float;	
    break;
  case PRECISION_FLOAT16:
    scale_row_no_resample = scale_row_no_resample_float16;	
    break;
  default:
    scale_row_no_resample = NULL;	
    break;
  } 
}


void 
scale_area_no_resample  (
                         PixelArea * src_area,
                         PixelArea * dest_area
                         )
{
  gint *x_src_offsets, *y_src_offsets;
  gint last_src_y;
  gint x,y,b;
  PixelRow src_row, dest_row;
  guchar *dest_row_data,  *src_row_data;
  Tag src_tag = pixelarea_tag (src_area);
  Tag dest_tag = pixelarea_tag (dest_area);
  gint orig_width = pixelarea_areawidth (src_area);
  gint orig_height = pixelarea_areaheight (src_area); 
  gint width = pixelarea_areawidth (dest_area);
  gint height = pixelarea_areaheight (dest_area); 
  gint bytes_per_pixel = tag_bytes (src_tag);
  gint num_channels = tag_num_channels (src_tag);
  gint row_num_channels;

  scale_area_no_resample_funcs (src_tag);

  /* get a dest row to copy src pixels to */ 
  dest_row_data = (guchar *) g_malloc (width * bytes_per_pixel); 
  pixelrow_init (&dest_row, dest_tag,(guchar *)dest_row_data, width);
  
  /* get a src row */
  src_row_data = (guchar *) g_malloc (orig_width * bytes_per_pixel); 
  pixelrow_init (&src_row, src_tag,(guchar *)src_row_data, orig_width);
  
  /* x and y scale tables  */
  x_src_offsets = (int *) g_malloc (width * num_channels * sizeof(int));
  y_src_offsets = (int *) g_malloc (height * sizeof(int));
  
  /*  pre-calc the scale tables  */
  for (b = 0; b < num_channels; b++)
    {
      for (x = 0; x < width; x++)
	{
	  x_src_offsets [b + x * num_channels] = b + num_channels * ((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_num_channels = width * num_channels;
  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); */
          pixelarea_copy_row (src_area, &src_row, 0, y_src_offsets[y], orig_width,  1);
          (*scale_row_no_resample) (&src_row, &dest_row, x_src_offsets);
	  last_src_y = y_src_offsets[y];
	}
      /* pixel_region_set_row (destPR, 0 , y, width, dest); */
      pixelarea_write_row (dest_area, &dest_row, 0, y, width );
    }
  
  g_free (x_src_offsets);
  g_free (y_src_offsets);
  g_free (dest_row_data);
  g_free (src_row_data);
}

static void 
scale_row_no_resample_u8  (
                           PixelRow * src_row,
                           PixelRow * dest_row,
                           gint * x_src_offsets
                           )
{
  gint x;
  guint8 *dest = (guint8*) pixelrow_data (dest_row);
  guint8 *src = (guint8*) pixelrow_data (src_row);		
  gint num_channels = tag_num_channels (pixelrow_tag (dest_row));
  gint row_num_channels = num_channels * pixelrow_width (dest_row);
  for (x = 0; x < row_num_channels ; x++)
    dest[x] = src[x_src_offsets[x]];
}
			
static void 
scale_row_no_resample_u16  (
                            PixelRow * src_row,
                            PixelRow * dest_row,
                            gint * x_src_offsets
                            )
{
  gint x;
  guint16 *dest = (guint16*) pixelrow_data (dest_row);
  guint16 *src = (guint16*) pixelrow_data (src_row);		
  gint num_channels = tag_num_channels (pixelrow_tag (dest_row));
  gint row_num_channels = num_channels * pixelrow_width (dest_row);
  for (x = 0; x < row_num_channels ; x++)
    dest[x] = src[x_src_offsets[x]];
}

static void 
scale_row_no_resample_float  (
                              PixelRow * src_row,
                              PixelRow * dest_row,
                              gint * x_src_offsets
                              )
{
  gint x;
  gfloat *dest = (gfloat*) pixelrow_data (dest_row);
  gfloat *src = (gfloat*) pixelrow_data (src_row);		
  gint num_channels = tag_num_channels (pixelrow_tag (dest_row));
  gint row_num_channels = num_channels * pixelrow_width (dest_row);
  for (x = 0; x < row_num_channels ; x++)
    dest[x] = src[x_src_offsets[x]];
}

static void 
scale_row_no_resample_float16  (
                              PixelRow * src_row,
                              PixelRow * dest_row,
                              gint * x_src_offsets
                              )
{
  gint x;
  guint16 *dest = (guint16*) pixelrow_data (dest_row);
  guint16 *src = (guint16*) pixelrow_data (src_row);		
  gint num_channels = tag_num_channels (pixelrow_tag (dest_row));
  gint row_num_channels = num_channels * pixelrow_width (dest_row);
  for (x = 0; x < row_num_channels ; x++)
    dest[x] = src[x_src_offsets[x]];
}

static ScaleRowResampleFunc
scale_row_resample_funcs (
                          Tag tag
                          )
     
{
  switch (tag_precision (tag))
  {
  case PRECISION_U8:
    return scale_row_resample_u8;	
  case PRECISION_U16:
    return scale_row_resample_u16;	
  case PRECISION_FLOAT:
    return scale_row_resample_float;	
  case PRECISION_FLOAT16:
    return scale_row_resample_float16;	
  case PRECISION_NONE:
  default:
    g_warning ("bad precision");
    break;
  }
  return NULL;
}

static ScaleSetDestRowFunc
scale_set_dest_funcs (
                      Tag tag
                      )
		  
{
  switch (tag_precision (tag))
  {
  case PRECISION_U8:
    return scale_set_dest_row_u8;	
  case PRECISION_U16:
    return scale_set_dest_row_u16;	
  case PRECISION_FLOAT:
    return scale_set_dest_row_float;	
  case PRECISION_FLOAT16:
    return scale_set_dest_row_float16;	
  case PRECISION_NONE:
  default:
    break;
  }
  return NULL;
}


void 
scale_area  (
             PixelArea * src_area,
             PixelArea * dest_area
             )
{
  gdouble *row_accum;
  gint src_row_num, dest_row_num, src_col_num;
  gdouble x_rat, y_rat;
  gdouble x_cum, y_cum;
  gdouble x_last, y_last;
  gdouble * x_frac, y_frac, tot_frac;
  gfloat  dy;
  gint i, x;
  gint advance_dest_y;
  ScaleType scale_type;
  guchar *src_row_data, *src_m1_row_data, *src_p1_row_data, *src_p2_row_data;
  PixelRow src_row, src_m1_row, src_p1_row, src_p2_row;
  /* guchar *dest_row_data; */
  guchar *src, *src_m1, *src_p1, *src_p2, *s;
  gint orig_width = pixelarea_areawidth (src_area);
  gint orig_height = pixelarea_areaheight (src_area);
  gint width = pixelarea_areawidth (dest_area);
  gint height = pixelarea_areaheight (dest_area);
  Tag src_tag = pixelarea_tag (src_area);
  Tag dest_tag = pixelarea_tag (dest_area);
  gint num_channels = tag_num_channels (dest_tag);
  gint bytes_per_pixel = tag_bytes (dest_tag);

 
  /* set up the function pointers for scale */ 
  ScaleRowResampleFunc scale_row_resample = scale_row_resample_funcs (src_tag);
  ScaleSetDestRowFunc  scale_set_dest_row = scale_set_dest_funcs (dest_tag);

  /* set up the src rows and data  */
  src_m1_row_data = (guchar *) g_malloc (orig_width * bytes_per_pixel);
  src_row_data    = (guchar *) g_malloc (orig_width * bytes_per_pixel);
  src_p1_row_data = (guchar *) g_malloc (orig_width * bytes_per_pixel);
  src_p2_row_data = (guchar *) g_malloc (orig_width * bytes_per_pixel);
  
  pixelrow_init (&src_m1_row, src_tag,(guchar *)src_m1_row_data, orig_width);
  pixelrow_init (&src_row, src_tag,(guchar *)src_row_data, orig_width);
  pixelrow_init (&src_p1_row, src_tag,(guchar *)src_p1_row_data, orig_width);
  pixelrow_init (&src_p2_row, src_tag,(guchar *)src_p2_row_data, orig_width);
  
  /*  find the ratios of old x to new x and old y to new y  */
  x_rat = (double) orig_width / (double) width;
  y_rat = (double) orig_height / (double) height;

  /*  determine the scale type  */
  if (x_rat < 1.0 && y_rat < 1.0)
    scale_type = MagnifyX_MagnifyY;
  else if (x_rat < 1.0 && y_rat >= 1.0)
    scale_type = MagnifyX_MinifyY;
  else if (x_rat >= 1.0 && y_rat < 1.0)
    scale_type = MinifyX_MagnifyY;
  else
    scale_type = MinifyX_MinifyY;

  /*  allocate an array to help with the calculations  */
  row_accum  = (double *) g_malloc (sizeof (double) * width * num_channels);
  x_frac = (double *) g_malloc (sizeof (double) * (width + orig_width));

  /*  initialize the pre-calculated pixel fraction array  */
  src_col_num = 0;
  x_cum = 0.0;
  x_last = 0.0;

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

  /*  clear the "row" array  */
  memset (row_accum, 0, sizeof (double) * width * num_channels);

  /*  counters zero...  */
  src_row_num = 0;
  dest_row_num = 0;
  y_cum = 0.0;
  y_last = 0.0;

  /*  Get the first src row  */
  /*pixel_region_get_row (srcPR, 0, src_row_num, orig_width, src, 1);*/
  pixelarea_copy_row (src_area, &src_row, 0, src_row_num, orig_width, 1); 

  /*  Get the next two if possible  */
  if (src_row_num < (orig_height - 1))
    /*pixel_region_get_row (srcPR, 0, (src_row_num + 1), orig_width, src_p1, 1);*/
    pixelarea_copy_row (src_area, &src_p1_row, 0, src_row_num + 1, orig_width, 1); 
  if ((src_row_num + 1) < (orig_height - 1))
    /*pixel_region_get_row (srcPR, 0, (src_row_num + 2), orig_width, src_p2, 1);*/
    pixelarea_copy_row (src_area, &src_p2_row, 0, src_row_num + 2, orig_width, 1); 
  /*  Scale the selected region  */
  i = height;
  while (i)
    {

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

      y_last += y_frac;

      /* set up generic guchar pointers to correct data */ 
      src = pixelrow_data (&src_row);
     
      if (src_row_num > 0)
	src_m1 = pixelrow_data (&src_m1_row);
      else
	src_m1 = pixelrow_data (&src_row);
      
      if (src_row_num < (orig_height - 1))
	src_p1 = pixelrow_data (&src_p1_row);
      else
	src_p1 = pixelrow_data (&src_row);

      if ((src_row_num + 1) < (orig_height - 1))
	src_p2 = pixelrow_data (&src_p2_row);
      else
        src_p2 = src_p1;
      
      /* scale and accumulate into row_accum array */ 
      (*scale_row_resample)(src, src_m1, src_p1, src_p2, dy, x_frac, y_frac, x_rat, 
			row_accum, num_channels, orig_width, width, scale_type); 

      if (advance_dest_y)
	{
	  tot_frac = 1.0 / (x_rat * y_rat);
	  
          /*  copy row_accum to "dest"  */
          for( x= 0; x < width * num_channels; x++)
            row_accum[x] *= tot_frac; 

          /* this sets dest row from array row_accum */
          (*scale_set_dest_row) (dest_area, dest_row_num, width, row_accum);
                          
          /*  clear row_accum  */
	  memset (row_accum, 0, sizeof (double) * width * num_channels);
          dest_row_num++;
	  i--;
	}
      else
	{
	  /*  Shuffle data in the rows  */

	  s = pixelrow_data (&src_m1_row); 
	  pixelrow_init (&src_m1_row, src_tag, pixelrow_data (&src_row), orig_width);
	  pixelrow_init (&src_row, src_tag, pixelrow_data (&src_p1_row), orig_width);
	  pixelrow_init (&src_p1_row, src_tag, pixelrow_data (&src_p2_row), orig_width);
	  pixelrow_init (&src_p2_row, src_tag, s, orig_width);
	  
	  src_row_num++;
  	  if ((src_row_num + 1) < (orig_height - 1))
	    /*pixel_region_get_row (srcPR, 0, (src_row_num + 2), orig_width, src_p2, 1);*/
            pixelarea_copy_row (src_area, &src_p2_row, 0, src_row_num + 2, orig_width, 1);
	}
    }

  /*  free up temporary arrays  */
  g_free (row_accum);
  g_free (x_frac);
  g_free (src_m1_row_data);
  g_free (src_row_data);
  g_free (src_p1_row_data);
  g_free (src_p2_row_data);
  /* g_free (dest_row_data); */
}


static void 
scale_set_dest_row_u8  (
                        PixelArea * area,
                        guint row,
                        guint width,
                        gdouble * row_accum
                        )
{
  Tag tag = pixelarea_tag (area);
  guint chan = tag_num_channels (tag);
  guint8 * dest = g_malloc (width * chan * sizeof (guint8));
  PixelRow temp;
  guint x;

  pixelrow_init (&temp, tag, dest, width);
  for (x = 0; x < width * chan; x++)
    {
      dest[x] = (guint8) row_accum[x];
    }
  pixelarea_write_row (area, &temp, 0, row, width);
  g_free (dest);
}


static void 
scale_set_dest_row_u16  (
                         PixelArea * area,
                         guint row,
                         guint width,
                         gdouble * row_accum
                         )
{
  Tag tag = pixelarea_tag (area);
  guint chan = tag_num_channels (tag);
  guint16 * dest = g_malloc (width * chan * sizeof (guint16));
  PixelRow temp;
  gint x;

  pixelrow_init (&temp, tag, (guchar *) dest, width);
  for (x = 0; x < width * chan; x++)
    {
      dest[x] = (guint16) row_accum[x];
    }
  pixelarea_write_row (area, &temp, 0, row, width);
  g_free (dest);
}


static void 
scale_set_dest_row_float  (
                           PixelArea * area,
                           guint row,
                           guint width,
                           gdouble * row_accum
                           )
{
  Tag tag = pixelarea_tag (area);
  guint chan = tag_num_channels (tag);
  gfloat * dest = g_malloc (width * chan * sizeof (gfloat));
  PixelRow temp;
  gint x;

  pixelrow_init (&temp, tag, (guchar *)dest, width);
  for (x = 0; x < width * chan; x++)
    {
      dest[x] = (gfloat) row_accum[x];
    }
  pixelarea_write_row (area, &temp, 0, row, width);
  g_free (dest);
}

static void 
scale_set_dest_row_float16  (
                           PixelArea * area,
                           guint row,
                           guint width,
                           gdouble * row_accum
                           )
{
  Tag tag = pixelarea_tag (area);
  guint chan = tag_num_channels (tag);
  guint16 * dest = g_malloc (width * chan * sizeof (guint16));
  PixelRow temp;
  gint x;
  ShortsFloat u;

  pixelrow_init (&temp, tag, (guchar *)dest, width);
  for (x = 0; x < width * chan; x++)
    {
      dest[x] = FLT16 (row_accum[x], u);
    }
  pixelarea_write_row (area, &temp, 0, row, width);
  g_free (dest);
}


static void 
scale_row_resample_u8  (
               guchar * src,
               guchar * src_m1,
               guchar * src_p1,
               guchar * src_p2,
               gdouble dy,
               gdouble * x_frac,
               gdouble y_frac,
               gdouble x_rat,
               gdouble * row_accum,
               gint num_channels,
               gint orig_width,
               gint width,
               gint scale_type
               )
{
  gdouble dx;
  gint b;
  gint advance_dest_x;
  gint minus_x, plus_x, plus2_x;
  gdouble tot_frac;
  
  /* cast the data to the right type */
  guint8 *s    = (guint8*)src;
  guint8 *s_m1 = (guint8*)src_m1;
  guint8 *s_p1 = (guint8*)src_p1;
  guint8 *s_p2 = (guint8*)src_p2;
  gint src_col_num = 0;
  gdouble x_cum = 0.0;
  gdouble *r = row_accum;   /*the acumulated array so far*/ 
  gint frac = 0;
  gint j = width;
  
  /* loop through the x values in dest */
 
  while (j)
    {
      if (x_cum + x_rat <= (src_col_num + 1 + EPSILON))
	{
	  x_cum += x_rat;
	  dx = x_cum - src_col_num;
	  advance_dest_x = TRUE;
	}
      else
	{
	  dx = 1.0;
	  advance_dest_x = FALSE;
	}

      tot_frac = x_frac[frac++] * y_frac;

      minus_x = (src_col_num > 0) ? -num_channels : 0;
      plus_x = (src_col_num < (orig_width - 1)) ? num_channels : 0;
      plus2_x = ((src_col_num + 1) < (orig_width - 1)) ? num_channels * 2 : plus_x;

      if (cubic_interpolation)
	switch (scale_type)
	  {
	  case MagnifyX_MagnifyY:
	    for (b = 0; b < num_channels; b++)
	      r[b] += cubic (dy, 
	           cubic (dx, s_m1[b+minus_x], s_m1[b], s_m1[b+plus_x], s_m1[b+plus2_x], 255.0, 0.0),
	           cubic (dx, s[b+minus_x], s[b], s[b+plus_x], s[b+plus2_x], 255.0, 0.0),
		   cubic (dx, s_p1[b+minus_x], s_p1[b], s_p1[b+plus_x], s_p1[b+plus2_x], 255.0, 0.0),
		   cubic (dx, s_p2[b+minus_x], s_p2[b], s_p2[b+plus_x], s_p2[b+plus2_x], 255.0, 0.0), 
		   255.0, 
		   0.0) * tot_frac;
	    break;
	  case MagnifyX_MinifyY:
	    for (b = 0; b < num_channels; b++)
	      r[b] += cubic (dx, s[b+minus_x], s[b], s[b+plus_x], s[b+plus2_x], 255.0, 0.0) * tot_frac;
	    break;
	  case MinifyX_MagnifyY:
	    for (b = 0; b < num_channels; b++)
	      r[b] += cubic (dy, s_m1[b], s[b], s_p1[b], s_p2[b], 255.0, 0.0) * tot_frac;
	    break;
	  case MinifyX_MinifyY:
	    for (b = 0; b < num_channels; b++)
	      r[b] += s[b] * tot_frac;
	    break;
	  }
      else
        {
	switch (scale_type)
	  {
	  case MagnifyX_MagnifyY:
	    for (b = 0; b < num_channels; b++)
	      r[b] += ((1 - dy) * ((1 - dx) * s[b] + dx * s[b+plus_x]) +
		       dy  * ((1 - dx) * s_p1[b] + dx * s_p1[b+plus_x])) * tot_frac;
	    break;
	  case MagnifyX_MinifyY:
	    for (b = 0; b < num_channels; b++)
	      r[b] += (s[b] * (1 - dx) + s[b+plus_x] * dx) * tot_frac;
	    break;
	  case MinifyX_MagnifyY:
	    for (b = 0; b < num_channels; b++)
	      r[b] += (s[b] * (1 - dy) + s_p1[b] * dy) * tot_frac;
	    break;
	  case MinifyX_MinifyY:
	    for (b = 0; b < num_channels; b++)
	      r[b] += s[b] * tot_frac;
	    break;
	  }
	}

      if (advance_dest_x)
	{
	  r += num_channels;
	  j--;
	}
      else
	{
	  s_m1 += num_channels;
	  s    += num_channels;
	  s_p1 += num_channels;
	  s_p2 += num_channels;
	  src_col_num++;
	}
    }
}

static void 
scale_row_resample_u16  (
                guchar * src,
                guchar * src_m1,
                guchar * src_p1,
                guchar * src_p2,
                gdouble dy,
                gdouble * x_frac,
                gdouble y_frac,
                gdouble x_rat,
                gdouble * row_accum,
                gint num_channels,
                gint orig_width,
                gint width,
                gint scale_type
                )
{
  gdouble dx;
  gint b;
  gint advance_dest_x;
  gint minus_x, plus_x, plus2_x;
  gdouble tot_frac;
  
  /* cast the data to the right type */
  guint16 *s    = (guint16*)src;
  guint16 *s_m1 = (guint16*)src_m1;
  guint16 *s_p1 = (guint16*)src_p1;
  guint16 *s_p2 = (guint16*)src_p2;
  gint src_col_num = 0;
  gdouble x_cum = 0.0;
  gdouble *r = row_accum;   /*the acumulated array so far*/ 
  gint frac = 0;
  gint j = width;
  
  /* loop through the x values in dest */
 
  while (j)
    {
      if (x_cum + x_rat <= (src_col_num + 1 + EPSILON))
	{
	  x_cum += x_rat;
	  dx = x_cum - src_col_num;
	  advance_dest_x = TRUE;
	}
      else
	{
	  dx = 1.0;
	  advance_dest_x = FALSE;
	}

      tot_frac = x_frac[frac++] * y_frac;

      minus_x = (src_col_num > 0) ? -num_channels : 0;
      plus_x = (src_col_num < (orig_width - 1)) ? num_channels : 0;
      plus2_x = ((src_col_num + 1) < (orig_width - 1)) ? num_channels * 2 : plus_x;

      if (cubic_interpolation)
	switch (scale_type)
	  {
	  case MagnifyX_MagnifyY:
	    for (b = 0; b < num_channels; b++)
	      r[b] += cubic ( dy, 
                   cubic (dx, s_m1[b+minus_x], s_m1[b], s_m1[b+plus_x], s_m1[b+plus2_x], 65535.0, 0.0),
		   cubic (dx, s[b+minus_x], s[b],	s[b+plus_x], s[b+plus2_x], 65535.0, 0.0),
		   cubic (dx, s_p1[b+minus_x], s_p1[b], s_p1[b+plus_x], s_p1[b+plus2_x], 65535.0, 0.0),
		   cubic (dx, s_p2[b+minus_x], s_p2[b], s_p2[b+plus_x], s_p2[b+plus2_x], 65535.0, 0.0), 
		   65535.0, 
		   0.0) * tot_frac;
	    break;
	  case MagnifyX_MinifyY:
	    for (b = 0; b < num_channels; b++)
	      r[b] += cubic (dx, s[b+minus_x], s[b], s[b+plus_x], s[b+plus2_x], 65535.0, 0.0) * tot_frac;
	    break;
	  case MinifyX_MagnifyY:
	    for (b = 0; b < num_channels; b++)
	      r[b] += cubic (dy, s_m1[b], s[b], s_p1[b], s_p2[b], 65535.0, 0.0) * tot_frac;
	    break;
	  case MinifyX_MinifyY:
	    for (b = 0; b < num_channels; b++)
	      r[b] += s[b] * tot_frac;
	    break;
	  }
      else
	switch (scale_type)
	  {
	  case MagnifyX_MagnifyY:
	    for (b = 0; b < num_channels; b++)
	      r[b] += ((1 - dy) * ((1 - dx) * s[b] + dx * s[b+plus_x]) +
		       dy  * ((1 - dx) * s_p1[b] + dx * s_p1[b+plus_x])) * tot_frac;
	    break;
	  case MagnifyX_MinifyY:
	    for (b = 0; b < num_channels; b++)
	      r[b] += (s[b] * (1 - dx) + s[b+plus_x] * dx) * tot_frac;
	    break;
	  case MinifyX_MagnifyY:
	    for (b = 0; b < num_channels; b++)
	      r[b] += (s[b] * (1 - dy) + s_p1[b] * dy) * tot_frac;
	    break;
	  case MinifyX_MinifyY:
	    for (b = 0; b < num_channels; b++)
	      r[b] += s[b] * tot_frac;
	    break;
	  }

      if (advance_dest_x)
	{
	  r += num_channels;
	  j--;
	}
      else
	{
	  s_m1 += num_channels;
	  s    += num_channels;
	  s_p1 += num_channels;
	  s_p2 += num_channels;
	  src_col_num++;
	}
    }
}

static void 
scale_row_resample_float  (
                  guchar * src,
                  guchar * src_m1,
                  guchar * src_p1,
                  guchar * src_p2,
                  gdouble dy,
                  gdouble * x_frac,
                  gdouble y_frac,
                  gdouble x_rat,
                  gdouble * row_accum,
                  gint num_channels,
                  gint orig_width,
                  gint width,
                  gint scale_type
                  )
{
  gdouble dx;
  gint b;
  gint advance_dest_x;
  gint minus_x, plus_x, plus2_x;
  gdouble tot_frac;
  
  /* cast the data to the right type */
  gfloat *s    = (gfloat*)src;
  gfloat *s_m1 = (gfloat*)src_m1;
  gfloat *s_p1 = (gfloat*)src_p1;
  gfloat *s_p2 = (gfloat*)src_p2;
  gint src_col_num = 0;
  gdouble x_cum = 0.0;
  gdouble *r = row_accum;   /*the acumulated array so far*/ 
  gint frac = 0;
  gint j = width;
  
  /* loop through the x values in dest */
 
  while (j)
    {
      if (x_cum + x_rat <= (src_col_num + 1 + EPSILON))
	{
	  x_cum += x_rat;
	  dx = x_cum - src_col_num;
	  advance_dest_x = TRUE;
	}
      else
	{
	  dx = 1.0;
	  advance_dest_x = FALSE;
	}

      tot_frac = x_frac[frac++] * y_frac;

      minus_x = (src_col_num > 0) ? -num_channels : 0;
      plus_x = (src_col_num < (orig_width - 1)) ? num_channels : 0;
      plus2_x = ((src_col_num + 1) < (orig_width - 1)) ? num_channels * 2 : plus_x;

      if (cubic_interpolation)
	switch (scale_type)
	  {
	  case MagnifyX_MagnifyY:
	    for (b = 0; b < num_channels; b++)
	      r[b] += cubic ( dy, 
                   cubic (dx, s_m1[b+minus_x], s_m1[b], s_m1[b+plus_x], s_m1[b+plus2_x], G_MAXFLOAT, G_MINFLOAT),
		   cubic (dx, s[b+minus_x], s[b],	s[b+plus_x], s[b+plus2_x], G_MAXFLOAT, G_MINFLOAT),
		   cubic (dx, s_p1[b+minus_x], s_p1[b], s_p1[b+plus_x], s_p1[b+plus2_x], G_MAXFLOAT, G_MINFLOAT),
		   cubic (dx, s_p2[b+minus_x], s_p2[b], s_p2[b+plus_x], s_p2[b+plus2_x], G_MAXFLOAT, G_MINFLOAT), 
		   G_MAXFLOAT, 
		   G_MINFLOAT) * tot_frac;
	    break;
	  case MagnifyX_MinifyY:
	    for (b = 0; b < num_channels; b++)
	      r[b] += cubic (dx, s[b+minus_x], s[b], s[b+plus_x], s[b+plus2_x], G_MAXFLOAT ,G_MINFLOAT) * tot_frac;
	    break;
	  case MinifyX_MagnifyY:
	    for (b = 0; b < num_channels; b++)
	      r[b] += cubic (dy, s_m1[b], s[b], s_p1[b], s_p2[b], G_MAXFLOAT, G_MINFLOAT) * tot_frac;
	    break;
	  case MinifyX_MinifyY:
	    for (b = 0; b < num_channels; b++)
	      r[b] += s[b] * tot_frac;
	    break;
	  }
      else
	switch (scale_type)
	  {
	  case MagnifyX_MagnifyY:
	    for (b = 0; b < num_channels; b++)
	      r[b] += ((1 - dy) * ((1 - dx) * s[b] + dx * s[b+plus_x]) +
		       dy  * ((1 - dx) * s_p1[b] + dx * s_p1[b+plus_x])) * tot_frac;
	    break;
	  case MagnifyX_MinifyY:
	    for (b = 0; b < num_channels; b++)
	      r[b] += (s[b] * (1 - dx) + s[b+plus_x] * dx) * tot_frac;
	    break;
	  case MinifyX_MagnifyY:
	    for (b = 0; b < num_channels; b++)
	      r[b] += (s[b] * (1 - dy) + s_p1[b] * dy) * tot_frac;
	    break;
	  case MinifyX_MinifyY:
	    for (b = 0; b < num_channels; b++)
	      r[b] += s[b] * tot_frac;
	    break;
	  }

      if (advance_dest_x)
	{
	  r += num_channels;
	  j--;
	}
      else
	{
	  s_m1 += num_channels;
	  s    += num_channels;
	  s_p1 += num_channels;
	  s_p2 += num_channels;
	  src_col_num++;
	}
    }
}

static void 
scale_row_resample_float16  (
                  guchar * src,
                  guchar * src_m1,
                  guchar * src_p1,
                  guchar * src_p2,
                  gdouble dy,
                  gdouble * x_frac,
                  gdouble y_frac,
                  gdouble x_rat,
                  gdouble * row_accum,
                  gint num_channels,
                  gint orig_width,
                  gint width,
                  gint scale_type
                  )
{
  gdouble dx;
  gint b;
  gint advance_dest_x;
  gint minus_x, plus_x, plus2_x;
  gdouble tot_frac;
  
  /* cast the data to the right type */
  guint16 *s    = (guint16*)src;
  guint16 *s_m1 = (guint16*)src_m1;
  guint16 *s_p1 = (guint16*)src_p1;
  guint16 *s_p2 = (guint16*)src_p2;
  gint src_col_num = 0;
  gdouble x_cum = 0.0;
  gdouble *r = row_accum;   /*the acumulated array so far*/ 
  gint frac = 0;
  gint j = width;
  ShortsFloat t, u, v, w;
  gfloat cubic1, cubic2, cubic3, cubic4;
  
  /* loop through the x values in dest */
 
  while (j)
    {
      if (x_cum + x_rat <= (src_col_num + 1 + EPSILON))
	{
	  x_cum += x_rat;
	  dx = x_cum - src_col_num;
	  advance_dest_x = TRUE;
	}
      else
	{
	  dx = 1.0;
	  advance_dest_x = FALSE;
	}

      tot_frac = x_frac[frac++] * y_frac;

      minus_x = (src_col_num > 0) ? -num_channels : 0;
      plus_x = (src_col_num < (orig_width - 1)) ? num_channels : 0;
      plus2_x = ((src_col_num + 1) < (orig_width - 1)) ? num_channels * 2 : plus_x;

      if (cubic_interpolation)
	switch (scale_type)
	  {
	  case MagnifyX_MagnifyY:
	    for (b = 0; b < num_channels; b++)
	      {
		   cubic1 = cubic (dx, FLT (s_m1[b+minus_x], u), FLT (s_m1[b], v), 
				FLT (s_m1[b+plus_x], w), FLT (s_m1[b+plus2_x], t), G_MAXFLOAT, G_MINFLOAT);
		   cubic2 = cubic (dx, FLT (s[b+minus_x], u), FLT (s[b], v), 
				FLT (s[b+plus_x], w), FLT (s[b+plus2_x], t), G_MAXFLOAT, G_MINFLOAT);
		   cubic3 = cubic (dx, FLT (s_p1[b+minus_x], u), FLT (s_p1[b], v), 
				FLT (s_p1[b+plus_x], w), FLT (s_p1[b+plus2_x], t), G_MAXFLOAT, G_MINFLOAT);
		   cubic4 = cubic (dx, FLT (s_p2[b+minus_x], u), FLT (s_p2[b], v), 
				FLT (s_p2[b+plus_x], w), FLT (s_p2[b+plus2_x], t), G_MAXFLOAT, G_MINFLOAT); 
	           r[b] += cubic ( dy, cubic1, cubic2, cubic3, cubic4, G_MAXFLOAT, G_MINFLOAT) * tot_frac;
	      }
	    break;
	  case MagnifyX_MinifyY:
	    for (b = 0; b < num_channels; b++)
	      r[b] += cubic (dx, FLT (s[b+minus_x], u), FLT (s[b], v), 
				FLT (s[b+plus_x], w), FLT (s[b+plus2_x], t), 
				G_MAXFLOAT ,G_MINFLOAT) * tot_frac;
	    break;
	  case MinifyX_MagnifyY:
	    for (b = 0; b < num_channels; b++)
	      r[b] += cubic (dy, FLT (s_m1[b], u), FLT (s[b], v), 
				FLT (s_p1[b], w), FLT (s_p2[b], t), 
				G_MAXFLOAT, G_MINFLOAT) * tot_frac;
	    break;
	  case MinifyX_MinifyY:
	    for (b = 0; b < num_channels; b++)
	      r[b] += FLT (s[b], u) * tot_frac;
	    break;
	  }
      else
	switch (scale_type)
	  {
	  case MagnifyX_MagnifyY:
	    for (b = 0; b < num_channels; b++)
	      r[b] += ((1 - dy) * ((1 - dx) * FLT (s[b], u) + dx * FLT (s[b+plus_x], v)) +
		       dy  * ((1 - dx) * FLT (s_p1[b], w) + dx * FLT (s_p1[b+plus_x], t))) * tot_frac;
	    break;
	  case MagnifyX_MinifyY:
	    for (b = 0; b < num_channels; b++)
	      r[b] += (FLT (s[b], u) * (1 - dx) + FLT (s[b+plus_x], v) * dx) * tot_frac;
	    break;
	  case MinifyX_MagnifyY:
	    for (b = 0; b < num_channels; b++)
	      r[b] += (FLT (s[b], u) * (1 - dy) + FLT (s_p1[b], v) * dy) * tot_frac;
	    break;
	  case MinifyX_MinifyY:
	    for (b = 0; b < num_channels; b++)
	      r[b] += FLT (s[b], u) * tot_frac;
	    break;
	  }

      if (advance_dest_x)
	{
	  r += num_channels;
	  j--;
	}
      else
	{
	  s_m1 += num_channels;
	  s    += num_channels;
	  s_p1 += num_channels;
	  s_p2 += num_channels;
	  src_col_num++;
	}
    }
}


float 
shapeburst_area  (
                  PixelArea * srcPR,
                  PixelArea * distPR
                  )
{
#define FIXME
#if 0
  Tile *tile;
  unsigned char *tile_data;
  float max_iterations;
  float *distp_cur;
  float *distp_prev;
  float *tmp;
  float min_prev;
  int min;
  int min_left;
  int length;
  int i, j, k;
  int src;
  int fraction;
  int prev_frac;
  int x, y;
  int end;
  int boundary;
  int inc;

  src = 0;

  max_iterations = 0.0;

  length = distPR->w + 1;
  distp_prev = (float *) paint_funcs_get_buffer (sizeof (float) * length * 2);
  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  */
      for (j = 0; j < length; j++)
	distp_cur[j - 1] = 0.0;

      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 = (int) 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)
		{
		  tile = tile_manager_get_tile (srcPR->tiles, x, y, 0);
		  tile_ref (tile);
		  tile_data = tile->data + (tile->ewidth * (y % TILE_HEIGHT) + (x % TILE_WIDTH));
		  boundary = MIN ((y % TILE_HEIGHT), (tile->ewidth - (x % TILE_WIDTH) - 1));
		  boundary = MIN (boundary, (y - end)) + 1;
		  inc = 1 - tile->ewidth;

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

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

		  tile_unref (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++;
	    }

	  distp_cur[j] = min + fraction / 256.0;

	  if (distp_cur[j] > max_iterations)
	    max_iterations = distp_cur[j];
	}

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

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

  return max_iterations;
#endif
  return 0.0;
}


static void 
thin_area_funcs (Tag tag)
{
  switch (tag_precision (tag))
  {
  case PRECISION_U8:
    thin_row = thin_row_u8;
    break;
  case PRECISION_U16:
    thin_row = thin_row_u16;
    break;
  case PRECISION_FLOAT:
    thin_row = thin_row_float;
    break;
  case PRECISION_FLOAT16:
    thin_row = thin_row_float16;
    break;
  default:
    thin_row = NULL;
    break;
  } 
}

gint 
thin_area  (
            PixelArea * src_area,
            gint type
            )
{
  guint i;
  int found_one;
  guchar *temp_data;

  PixelRow prev_row;
  PixelRow cur_row;
  PixelRow next_row;
  PixelRow dest_row;
  
  Tag src_tag = pixelarea_tag (src_area);
  gint bytes_per_pixel = tag_bytes (src_tag);
  guint width = pixelarea_areawidth (src_area);
  guint height = pixelarea_areaheight (src_area);
  gint src_x = pixelarea_x (src_area); 
  gint src_y = pixelarea_y (src_area); 

  /* get buffers for data for prev, cur and next rows , 2 extra pixels for ends */
  guchar *prev_row_data = (guchar *) g_malloc ((width + 2) * bytes_per_pixel); 
  guchar *next_row_data = (guchar *) g_malloc ((width + 2) * bytes_per_pixel); 
  guchar *cur_row_data = (guchar *) g_malloc ((width + 2) * bytes_per_pixel); 
  
  /* get buffer for the dest row */
  guchar *dest_row_data = (guchar *) g_malloc (width  * bytes_per_pixel); 
  pixelrow_init (&dest_row, src_tag, dest_row_data, width);
 
  thin_area_funcs (src_tag);
 
  /* clear buffers to 0 */
  memset (prev_row_data, 0, (width + 2) * bytes_per_pixel);
  memset (next_row_data, 0, (width + 2) * bytes_per_pixel);
  memset (cur_row_data, 0, (width + 2) * bytes_per_pixel);
  
  /* set the PixelRow buffers, inset one pixel */ 
  pixelrow_init (&prev_row, src_tag, prev_row_data + bytes_per_pixel, width);
  pixelrow_init (&cur_row, src_tag, cur_row_data + bytes_per_pixel, width);
  pixelrow_init (&next_row, src_tag, next_row_data + bytes_per_pixel, width);
 
  /* load the first row of src_area into cur_row */
  pixelarea_copy_row (src_area, &cur_row, src_x, src_y, width, 1);      
  
  found_one = FALSE;
  for (i = 0; i < height; i++)
    {
      if (i < (height - 1))
        /*pixel_region_get_row (src,src->x,src->y + i + 1, src->w, cur_row, 1);*/
        pixelarea_copy_row (src_area, &next_row, src_x, src_y + i + 1, width,1);
      else
        memset (pixelrow_data (&next_row), 0, width  * bytes_per_pixel);
      

      found_one = (*thin_row) (&cur_row, &next_row, &prev_row, &dest_row, type);

      pixelarea_write_row (src_area, &dest_row, src_x, src_y + i, width);
      
      /* shuffle prev, cur, next buffers around */
      
      temp_data = pixelrow_data (&prev_row);
      pixelrow_init (&prev_row, src_tag, pixelrow_data (&cur_row), width);
      pixelrow_init (&cur_row, src_tag, pixelrow_data (&next_row), width);
      pixelrow_init (&next_row, src_tag, temp_data, width);
    }
  g_free (prev_row_data);
  g_free (next_row_data);
  g_free (cur_row_data);
  g_free (dest_row_data);
  return (found_one == FALSE);  /*  is the area empty yet?  */
}

static gint 
thin_row_u8  (
              PixelRow * cur_row,
              PixelRow * next_row,
              PixelRow * prev_row,
              PixelRow * dest_row,
              gint type
              )
{
  gint j;
  gint found_one = FALSE;
  guint8 *cur = (guint8*) pixelrow_data (cur_row);
  guint8 *next =(guint8*) pixelrow_data (next_row);
  guint8 *prev =(guint8*) pixelrow_data (prev_row);
  guint8 *dest =(guint8*) pixelrow_data (dest_row);
  gint width = pixelrow_width (cur_row);
  for (j = 0; j < width; j++)
    {
      if (type == SHRINK_REGION)
	{
	  if (cur[j] != 0)
	    {
	      found_one = TRUE;
	      dest[j] = cur[j];

	      if (cur[j - 1] < dest[j])
		dest[j] = cur[j - 1];
	      if (cur[j + 1] < dest[j])
		dest[j] = cur[j + 1];
	      if (prev[j] < dest[j])
		dest[j] = prev[j];
	      if (next[j] < dest[j])
		dest[j] = next[j];
	    }
	  else
	    dest[j] = cur[j];
	}
      else
	{
	  if (cur[j] != 255)
	    {
	      found_one = TRUE;
	      dest[j] = cur[j];

	      if (cur[j - 1] > dest[j])
		dest[j] = cur[j - 1];
	      if (cur[j + 1] > dest[j])
		dest[j] = cur[j + 1];
	      if (prev[j] > dest[j])
		dest[j] = prev[j];
	      if (next[j] > dest[j])
		dest[j] = next[j];
	    }
	  else
	    dest[j] = cur[j];
	}
    }
   return found_one;
}

static gint 
thin_row_u16  (
               PixelRow * cur_row,
               PixelRow * next_row,
               PixelRow * prev_row,
               PixelRow * dest_row,
               gint type
               )
{
  gint j;
  gint found_one = FALSE;
  guint16 *cur = (guint16*) pixelrow_data (cur_row);
  guint16 *next =(guint16*) pixelrow_data (next_row);
  guint16 *prev =(guint16*) pixelrow_data (prev_row);
  guint16 *dest =(guint16*) pixelrow_data (dest_row);
  gint width = pixelrow_width (cur_row);

  for (j = 0; j < width; j++)
    {
      if (type == SHRINK_REGION)
	{
	  if (cur[j] != 0)
	    {
	      found_one = TRUE;
	      dest[j] = cur[j];

	      if (cur[j - 1] < dest[j])
		dest[j] = cur[j - 1];
	      if (cur[j + 1] < dest[j])
		dest[j] = cur[j + 1];
	      if (prev[j] < dest[j])
		dest[j] = prev[j];
	      if (next[j] < dest[j])
		dest[j] = next[j];
	    }
	  else
	    dest[j] = cur[j];
	}
      else
	{
	  if (cur[j] != 65535)
	    {
	      found_one = TRUE;
	      dest[j] = cur[j];

	      if (cur[j - 1] > dest[j])
		dest[j] = cur[j - 1];
	      if (cur[j + 1] > dest[j])
		dest[j] = cur[j + 1];
	      if (prev[j] > dest[j])
		dest[j] = prev[j];
	      if (next[j] > dest[j])
		dest[j] = next[j];
	    }
	  else
	    dest[j] = cur[j];
	}
    }
   return found_one;
}

static gint 
thin_row_float  (
                 PixelRow * cur_row,
                 PixelRow * next_row,
                 PixelRow * prev_row,
                 PixelRow * dest_row,
                 gint type
                 )
{
  gint j;
  gint found_one = FALSE;
  gfloat *cur = (gfloat*) pixelrow_data (cur_row);
  gfloat *next =(gfloat*) pixelrow_data (next_row);
  gfloat *prev =(gfloat*) pixelrow_data (prev_row);
  gfloat *dest =(gfloat*) pixelrow_data (dest_row);
  gint width = pixelrow_width (cur_row);

  for (j = 0; j < width; j++)
    {
      if (type == SHRINK_REGION)
	{
	  if (cur[j] != 0.0)
	    {
	      found_one = TRUE;
	      dest[j] = cur[j];

	      if (cur[j - 1] < dest[j])
		dest[j] = cur[j - 1];
	      if (cur[j + 1] < dest[j])
		dest[j] = cur[j + 1];
	      if (prev[j] < dest[j])
		dest[j] = prev[j];
	      if (next[j] < dest[j])
		dest[j] = next[j];
	    }
	  else
	    dest[j] = cur[j];
	}
      else
	{
	  if (cur[j] != 1.0)
	    {
	      found_one = TRUE;
	      dest[j] = cur[j];

	      if (cur[j - 1] > dest[j])
		dest[j] = cur[j - 1];
	      if (cur[j + 1] > dest[j])
		dest[j] = cur[j + 1];
	      if (prev[j] > dest[j])
		dest[j] = prev[j];
	      if (next[j] > dest[j])
		dest[j] = next[j];
	    }
	  else
	    dest[j] = cur[j];
	}
    }
   return found_one;
}

static gint 
thin_row_float16  (
                 PixelRow * cur_row,
                 PixelRow * next_row,
                 PixelRow * prev_row,
                 PixelRow * dest_row,
                 gint type
                 )
{
  gint j;
  gint found_one = FALSE;
  guint16 *cur = (guint16 *) pixelrow_data (cur_row);
  guint16 *next =(guint16 *) pixelrow_data (next_row);
  guint16 *prev =(guint16 *) pixelrow_data (prev_row);
  guint16 *dest =(guint16 *) pixelrow_data (dest_row);
  gint width = pixelrow_width (cur_row);
  ShortsFloat u;
  gfloat destj;

  for (j = 0; j < width; j++)
    {
      if (type == SHRINK_REGION)
	{
	  if (cur[j] != ZERO_FLOAT16)
	    {
	      found_one = TRUE;
	      dest[j] = cur[j];
	      destj = FLT (dest[j], u);

	      if (FLT (cur[j - 1], u) < destj)
		dest[j] = cur[j - 1];
	      if (FLT (cur[j + 1], u) < destj)
		dest[j] = cur[j + 1];
	      if (FLT (prev[j], u) < destj)
		dest[j] = prev[j];
	      if (FLT (next[j], u) < destj)
		dest[j] = next[j];
	    }
	  else
	    dest[j] = cur[j];
	}
      else
	{
	  if (cur[j] != ONE_FLOAT16)
	    {
	      found_one = TRUE;
	      dest[j] = cur[j];
	      destj = FLT (dest[j], u);

	      if (FLT (cur[j - 1], u) > destj)
		dest[j] = cur[j - 1];
	      if (FLT (cur[j + 1], u) > destj)
		dest[j] = cur[j + 1];
	      if (FLT (prev[j], u) > destj)
		dest[j] = prev[j];
	      if (FLT (next[j], u) > destj)
		dest[j] = next[j];
	    }
	  else
	    dest[j] = cur[j];
	}
    }
   return found_one;
}

typedef void (*SwapRowFunc) (PixelRow*, PixelRow*);
static SwapRowFunc swap_area_funcs (Tag);

static SwapRowFunc 
swap_area_funcs (
		Tag tag
		)
	  
{
  switch (tag_precision (tag))
  {
  case PRECISION_U8:
    return swap_row_u8;
  case PRECISION_U16:
    return swap_row_u16;
  case PRECISION_FLOAT:
    return swap_row_float;
  case PRECISION_FLOAT16:
    return swap_row_float16;
  default:
    return NULL;
  } 
}

void 
swap_area  (
            PixelArea * src_area,
            PixelArea * dest_area
            )
{
  void * pag;
  Tag src_tag = pixelarea_tag (src_area); 
  SwapRowFunc swap_row = swap_area_funcs (src_tag);
   /* put in tags check */
  
  for (pag = pixelarea_register (2, src_area, dest_area);
       pag != NULL;
       pag = pixelarea_process (pag))
    {
      PixelRow src_row;
      PixelRow dest_row;
 
      gint h = pixelarea_height (src_area);
      while (h--)
        {
          pixelarea_getdata (src_area, &src_row, h);
          pixelarea_getdata (dest_area, &dest_row, h);
            
          /* swap_pixels (s, d, src->w * bytes); */
          (*swap_row) (&src_row, &dest_row);
        }
    }
}

typedef void (*ApplyMaskToAlphaChannelRowFunc) (PixelRow*, PixelRow*, gfloat);
static ApplyMaskToAlphaChannelRowFunc apply_mask_to_area_funcs (Tag);

static ApplyMaskToAlphaChannelRowFunc 
apply_mask_to_area_funcs (
		Tag tag
		)
	  
{
  switch (tag_precision (tag))
  {
  case PRECISION_U8:
    return apply_mask_to_alpha_channel_row_u8;
  case PRECISION_U16:
    return apply_mask_to_alpha_channel_row_u16;
  case PRECISION_FLOAT:
    return apply_mask_to_alpha_channel_row_float;
  case PRECISION_FLOAT16:
    return apply_mask_to_alpha_channel_row_float16;
  default:
    return NULL;
  } 
}

void 
apply_mask_to_area  (
                     PixelArea * src_area,
                     PixelArea * mask_area,
                     gfloat opacity
                     )
{
  void *  pag;
  Tag src_tag = pixelarea_tag (src_area); 
  ApplyMaskToAlphaChannelRowFunc apply_mask_to_alpha_channel_row = apply_mask_to_area_funcs (src_tag);

  /* put in tags check */
  
  for (pag = pixelarea_register (2, src_area, mask_area);
       pag != NULL;
       pag = pixelarea_process (pag))
    {
      PixelRow srcrow;
      PixelRow maskrow;
      int h;
      
      h = pixelarea_height (src_area);
      while (h--)
	{
	  pixelarea_getdata (src_area, &srcrow, h);
	  pixelarea_getdata (mask_area, &maskrow, h);

          /*apply_mask_to_alpha_channel(s, m, opacity, src-> w, srcPR->bytes);*/
          (*apply_mask_to_alpha_channel_row) (&srcrow, &maskrow, opacity);
	}
    }
}

typedef void (*CombineMaskAndAlphaChannelRowFunc) (PixelRow*, PixelRow*, gfloat);
static CombineMaskAndAlphaChannelRowFunc  combine_mask_and_area_funcs(Tag);

static CombineMaskAndAlphaChannelRowFunc 
combine_mask_and_area_funcs (
		Tag tag
		)
	  
{
  switch (tag_precision (tag))
  {
  case PRECISION_U8:
    return combine_mask_and_alpha_channel_row_u8;
  case PRECISION_U16:
    return combine_mask_and_alpha_channel_row_u16;
  case PRECISION_FLOAT:
    return combine_mask_and_alpha_channel_row_float;
  case PRECISION_FLOAT16:
    return combine_mask_and_alpha_channel_row_float16;
  default:
    return NULL;
  } 
}

void 
combine_mask_and_area  (
                        PixelArea * src_area,
                        PixelArea * mask_area,
                        gfloat opacity
                        )
{
  void *  pag;
  Tag src_tag = pixelarea_tag (src_area); 
  CombineMaskAndAlphaChannelRowFunc combine_mask_and_alpha_channel_row = 
			combine_mask_and_area_funcs (src_tag);

  /*put in tags check */
  
  for (pag = pixelarea_register (2, src_area, mask_area);
       pag != NULL;
       pag = pixelarea_process (pag))
    {
      PixelRow srcrow;
      PixelRow maskrow;
      int h;
      
      h = pixelarea_height (src_area);
      while (h--)
	{
	  pixelarea_getdata (src_area, &srcrow, h);
	  pixelarea_getdata (mask_area, &maskrow, h);

  	  /*combine_mask_and_alpha_channel (s, m, opacity, src->w,src->bytes);*/
	  (*combine_mask_and_alpha_channel_row) (&srcrow, &maskrow, opacity);
	}
    }
}

typedef void (*CopyGrayToIntenARowFunc) (PixelRow*, PixelRow*);
static CopyGrayToIntenARowFunc  copy_gray_to_area_funcs(Tag);

static CopyGrayToIntenARowFunc  
copy_gray_to_area_funcs (
		Tag tag
		)
	  
{
  switch (tag_precision (tag))
  {
  case PRECISION_U8:
    return copy_gray_to_inten_a_row_u8;
  case PRECISION_U16:
    return copy_gray_to_inten_a_row_u16;
  case PRECISION_FLOAT:
    return copy_gray_to_inten_a_row_float;
  case PRECISION_FLOAT16:
    return copy_gray_to_inten_a_row_float16;
  default:
    return NULL;
  } 
}

#define FIXME /* this can go */
void 
copy_gray_to_area  (
                    PixelArea * src_area,
                    PixelArea * dest_area
                    )
{
  void *  pag;
  Tag src_tag = pixelarea_tag (src_area); 
  CopyGrayToIntenARowFunc copy_gray_to_inten_a_row = 
			copy_gray_to_area_funcs (src_tag);


   /*put in tags check*/
  
  for (pag = pixelarea_register (2, src_area, dest_area);
       pag != NULL;
       pag = pixelarea_process (pag))
    {
      PixelRow src_row;
      PixelRow dest_row;
 
      gint h = pixelarea_height (src_area);
      while (h--)
        {
          pixelarea_getdata (src_area, &src_row, h);
          pixelarea_getdata (dest_area, &dest_row, h);
            
  	  /*copy_gray_to_inten_a_pixels (s, d, src->w, dest->bytes);*/
         (*copy_gray_to_inten_a_row) (&src_row, &dest_row);
        }
    }
}


static void initial_area_funcs (Tag);
typedef void (*InitialChannelRowFunc) (PixelRow *,PixelRow *);
typedef void (*InitialIndexedRowFunc) (PixelRow *, PixelRow *, unsigned char *);
typedef void (*InitialIndexedARowFunc) (PixelRow *, PixelRow *, PixelRow *, unsigned char *, gfloat);
typedef void (*DissolveRowFunc) (PixelRow *, PixelRow *, gint , gint, gfloat);
typedef void (*InitialIntenRowFunc) (PixelRow *, PixelRow *, PixelRow *, gfloat, gint *);
typedef void (*InitialIntenARowFunc) (PixelRow *, PixelRow *, PixelRow *, gfloat, gint *);
static InitialChannelRowFunc initial_channel_row;
static InitialIndexedRowFunc initial_indexed_row;
static InitialIndexedARowFunc initial_indexed_a_row;
static DissolveRowFunc dissolve_row;
static InitialIntenRowFunc initial_inten_row;
static InitialIntenARowFunc initial_inten_a_row;

static void 
initial_area_funcs (
		Tag tag
		)
	  
{
  switch (tag_precision (tag))
  {
  case PRECISION_U8:
    initial_channel_row = initial_channel_row_u8; 
    initial_indexed_row = initial_indexed_row_u8; 
    initial_indexed_a_row = initial_indexed_a_row_u8; 
    dissolve_row = dissolve_row_u8; 
    initial_inten_row = initial_inten_row_u8; 
    initial_inten_a_row = initial_inten_a_row_u8; 
    break;
  case PRECISION_U16:
    initial_channel_row = initial_channel_row_u16; 
    initial_indexed_row = initial_indexed_row_u16; 
    initial_indexed_a_row = initial_indexed_a_row_u16; 
    dissolve_row = dissolve_row_u16; 
    initial_inten_row = initial_inten_row_u16; 
    initial_inten_a_row = initial_inten_a_row_u16; 
    break;
  case PRECISION_FLOAT:
    initial_channel_row = initial_channel_row_float; 
    initial_indexed_row = NULL; 
    initial_indexed_a_row = NULL; 
    dissolve_row = dissolve_row_float; 
    initial_inten_row = initial_inten_row_float; 
    initial_inten_a_row = initial_inten_a_row_float; 
    break;
  case PRECISION_FLOAT16:
    initial_channel_row = initial_channel_row_float16; 
    initial_indexed_row = NULL; 
    initial_indexed_a_row = NULL; 
    dissolve_row = dissolve_row_float16; 
    initial_inten_row = initial_inten_row_float16; 
    initial_inten_a_row = initial_inten_a_row_float16; 
    break;
  default:
    initial_channel_row = NULL; 
    initial_indexed_row = NULL; 
    initial_indexed_a_row = NULL; 
    dissolve_row = NULL; 
    initial_inten_row = NULL; 
    initial_inten_a_row = NULL; 
    break;
  } 
}


void 
initial_area  (
               PixelArea * src_area,
               PixelArea * dest_area,
               PixelArea * mask_area,
               unsigned char * data,   /* data is a cmap or color if needed */
               gfloat opacity,
               gint mode,
               gint * affect,
               gint type
               )
{
  void *  pag;
  gint buf_size;
  PixelRow buf_row;
  guchar *buf_row_data = 0; 
  Tag src_tag = pixelarea_tag (src_area); 
  gint src_width = pixelarea_areawidth (src_area);
  gint src_num_channels = tag_num_channels (src_tag);
  gint src_bytes = tag_bytes (src_tag); /* per pixel */
  gint src_bytes_per_channel = src_bytes / src_num_channels;
  Precision prec = tag_precision(src_tag); 
  /*put in tags check*/
  initial_area_funcs (src_tag);
  /* get a buffer for dissolve if needed */
  
  if ( (type == INITIAL_INTENSITY && mode == DISSOLVE_MODE) ||
       (type == INITIAL_INTENSITY_ALPHA && mode == DISSOLVE_MODE) ) 
  {	 
    Tag buf_tag = tag_new (prec, tag_format (src_tag), ALPHA_YES);
    
    if ( tag_alpha(src_tag) == ALPHA_YES )
      buf_size = src_width * src_bytes;
    else
      buf_size = src_width * (src_bytes + src_bytes_per_channel);
    
    /* allocate the buf_rows data */  
    buf_row_data = (guchar *) g_malloc (buf_size);
    pixelrow_init (&buf_row, buf_tag , buf_row_data, src_width); 
  } 
  
  for (pag = pixelarea_register (3, src_area, dest_area, mask_area);
       pag != NULL;
       pag = pixelarea_process (pag))
    {
      PixelRow src_row;
      PixelRow dest_row;
      PixelRow mask_row;
 
      gint h = pixelarea_height (src_area);
      gint src_x = pixelarea_x (src_area);
      gint src_y = pixelarea_y (src_area);
      while (h--)
        {
          pixelarea_getdata (src_area, &src_row, h);
          pixelarea_getdata (dest_area, &dest_row, h);
          pixelarea_getdata (mask_area, &mask_row, h);
            
	  switch (type)
	    {
	    case INITIAL_CHANNEL_MASK:
	    case INITIAL_CHANNEL_SELECTION:
  	      /*initial_channel_pixels (s, d, src->w, dest->bytes);*/
              (*initial_channel_row) (&src_row, &dest_row);
	      break;

	    case INITIAL_INDEXED:
  	      /*initial_indexed_pixels (s, d, data, src->w);*/
              (*initial_indexed_row) (&src_row, &dest_row, data);
	      break;

	    case INITIAL_INDEXED_ALPHA:
  	      /*initial_indexed_a_pixels (s, d, m, data, opacity, src->w);*/
              (*initial_indexed_a_row) (&src_row, &dest_row, &mask_row, data, opacity);
	      break;

	    case INITIAL_INTENSITY:
	      if (mode == DISSOLVE_MODE)
		{
  		  /*dissolve_pixels (s, buf, src->x, src->y + h, opacity, src->w, src->bytes, src->bytes + 1, 0);*/
  		  /*initial_inten_pixels (buf, d, m, opacity, affect, src->w,  src->bytes);*/
 		  (*dissolve_row) (&src_row, &buf_row, src_x, src_y + h, opacity);
                  (*initial_inten_row) (&buf_row, &dest_row, &mask_row, opacity, affect);
		}
	      else
		/*initial_inten_pixels (s, d, m, opacity, affect, src->w, pixel_region_bytes (src));*/
                (*initial_inten_row) (&src_row, &dest_row, &mask_row, opacity, affect);
	      break;

	    case INITIAL_INTENSITY_ALPHA:
	      if (mode == DISSOLVE_MODE)
		{
		  /* dissolve_pixels (s, buf, src->x, src->y + h, opacity, src->w, src->bytes, src->bytes, 1);*/
		  /* initial_inten_a_pixels (buf, d, m, opacity, affect, src->w, src->bytes);*/
                  (*dissolve_row) (&src_row, &buf_row, src_x, src_y + h, opacity);
                  (*initial_inten_a_row) (&buf_row, &dest_row, &mask_row, opacity, affect);
		}
	      else
		/*initial_inten_a_pixels (s, d, m, opacity, affect, src->w, src->bytes);*/
                (*initial_inten_a_row) (&src_row, &dest_row, &mask_row, opacity, affect);
	      break;
	    }
        }
    } 
    if (buf_row_data)
      g_free (buf_row_data);
}

static void combine_areas_funcs (Tag);
typedef void (*CombineIndexedAndIndexedRowFunc) (PixelRow *, PixelRow *, PixelRow *, PixelRow *, gfloat, gint *);
typedef void (*CombineIndexedAndIndexedARowFunc) (PixelRow *, PixelRow *, PixelRow *, PixelRow *, gfloat, gint *);
typedef void (*CombineIndexedAAndIndexedARowFunc) (PixelRow *, PixelRow *, PixelRow *, PixelRow *, gfloat, gint *);
typedef void (*CombineIntenAAndIndexedARowFunc) (PixelRow *, PixelRow *, PixelRow *, PixelRow *, unsigned char *, gfloat);
typedef void (*CombineIntenAAndChannelMaskRowFunc) (PixelRow *, PixelRow *, PixelRow *, PixelRow *, gfloat);
typedef void (*CombineIntenAAndChannelSelectionRowFunc) (PixelRow *, PixelRow *, PixelRow *, PixelRow *, gfloat);
typedef void (*CombineIntenAndIntenRowFunc) (PixelRow *, PixelRow *, PixelRow *, PixelRow *, gfloat, gint *);
typedef void (*CombineIntenAndIntenARowFunc) (PixelRow *, PixelRow *, PixelRow *, PixelRow *, gfloat, gint *);
typedef void (*CombineIntenAAndIntenRowFunc) (PixelRow *, PixelRow *, PixelRow *, PixelRow *, gfloat, gint *,gint);
typedef void (*CombineIntenAAndIntenARowFunc) (PixelRow *, PixelRow *, PixelRow *, PixelRow *, gfloat, gint *,gint);
typedef void (*BehindIntenRowFunc) (PixelRow *, PixelRow *, PixelRow *, PixelRow *, gfloat, gint *);
typedef void (*BehindIndexedRowFunc) (PixelRow *, PixelRow *, PixelRow *, PixelRow *, gfloat, gint *);
typedef void (*ReplaceIntenRowFunc) (PixelRow *, PixelRow *, PixelRow *, PixelRow *, gfloat, gint *);
typedef void (*ReplaceIndexedRowFunc) (PixelRow *, PixelRow *, PixelRow *, PixelRow *, gfloat, gint *);
typedef void (*EraseIntenRowFunc) (PixelRow *, PixelRow *, PixelRow *, PixelRow *, gfloat, gint *);
typedef void (*EraseIndexedRowFunc) (PixelRow *, PixelRow *, PixelRow *, PixelRow *, gfloat, gint *);
static CombineIndexedAndIndexedRowFunc combine_indexed_and_indexed_row;
static CombineIndexedAndIndexedARowFunc combine_indexed_and_indexed_a_row;
static CombineIndexedAAndIndexedARowFunc combine_indexed_a_and_indexed_a_row;
static CombineIntenAAndIndexedARowFunc combine_inten_a_and_indexed_a_row;
static CombineIntenAAndChannelMaskRowFunc combine_inten_a_and_channel_mask_row;
static CombineIntenAAndChannelSelectionRowFunc combine_inten_a_and_channel_selection_row;
static CombineIntenAndIntenRowFunc combine_inten_and_inten_row;
static CombineIntenAndIntenARowFunc combine_inten_and_inten_a_row;
static CombineIntenAAndIntenRowFunc combine_inten_a_and_inten_row;
static CombineIntenAAndIntenARowFunc combine_inten_a_and_inten_a_row;
static BehindIntenRowFunc behind_inten_row;
static BehindIndexedRowFunc behind_indexed_row;
static ReplaceIntenRowFunc replace_inten_row;
static ReplaceIndexedRowFunc replace_indexed_row;
static EraseIntenRowFunc erase_inten_row;
static EraseIndexedRowFunc erase_indexed_row;

static void 
combine_areas_funcs (
		Tag tag
		)
	  
{
  switch (tag_precision (tag))
  {
  case PRECISION_U8:
    combine_indexed_and_indexed_row = combine_indexed_and_indexed_row_u8;
    combine_indexed_and_indexed_a_row = combine_indexed_and_indexed_a_row_u8; 
    combine_indexed_a_and_indexed_a_row = combine_indexed_a_and_indexed_a_row_u8;
    combine_inten_a_and_indexed_a_row = combine_inten_a_and_indexed_a_row_u8; 
    combine_inten_a_and_channel_mask_row = combine_inten_a_and_channel_mask_row_u8; 
    combine_inten_a_and_channel_selection_row = combine_inten_a_and_channel_selection_row_u8; 
    combine_inten_and_inten_row = combine_inten_and_inten_row_u8; 
    combine_inten_and_inten_a_row = combine_inten_and_inten_a_row_u8; 
    combine_inten_a_and_inten_row = combine_inten_a_and_inten_row_u8; 
    combine_inten_a_and_inten_a_row = combine_inten_a_and_inten_a_row_u8; 
    behind_inten_row = behind_inten_row_u8; 
    behind_indexed_row = behind_indexed_row_u8; 
    replace_inten_row = replace_inten_row_u8; 
    replace_indexed_row = replace_indexed_row_u8; 
    erase_inten_row = erase_inten_row_u8; 
    erase_indexed_row = erase_indexed_row_u8;
    break;
  case PRECISION_U16:
    combine_indexed_and_indexed_row = combine_indexed_and_indexed_row_u16;
    combine_indexed_and_indexed_a_row = combine_indexed_and_indexed_a_row_u16; 
    combine_indexed_a_and_indexed_a_row = combine_indexed_a_and_indexed_a_row_u16;
    combine_inten_a_and_indexed_a_row = combine_inten_a_and_indexed_a_row_u16; 
    combine_inten_a_and_channel_mask_row = combine_inten_a_and_channel_mask_row_u16; 
    combine_inten_a_and_channel_selection_row = combine_inten_a_and_channel_selection_row_u16; 
    combine_inten_and_inten_row = combine_inten_and_inten_row_u16; 
    combine_inten_and_inten_a_row = combine_inten_and_inten_a_row_u16; 
    combine_inten_a_and_inten_row = combine_inten_a_and_inten_row_u16; 
    combine_inten_a_and_inten_a_row = combine_inten_a_and_inten_a_row_u16; 
    behind_inten_row = behind_inten_row_u16; 
    behind_indexed_row = behind_indexed_row_u16; 
    replace_inten_row = replace_inten_row_u16; 
    replace_indexed_row = replace_indexed_row_u16; 
    erase_inten_row = erase_inten_row_u16; 
    erase_indexed_row = erase_indexed_row_u16;
    break;
  case PRECISION_FLOAT:
    combine_indexed_and_indexed_row = NULL;
    combine_indexed_and_indexed_a_row = NULL; 
    combine_indexed_a_and_indexed_a_row = NULL;
    combine_inten_a_and_indexed_a_row = NULL; 
    combine_inten_a_and_channel_mask_row = combine_inten_a_and_channel_mask_row_float; 
    combine_inten_a_and_channel_selection_row = combine_inten_a_and_channel_selection_row_float; 
    combine_inten_and_inten_row = combine_inten_and_inten_row_float; 
    combine_inten_and_inten_a_row = combine_inten_and_inten_a_row_float; 
    combine_inten_a_and_inten_row = combine_inten_a_and_inten_row_float; 
    combine_inten_a_and_inten_a_row = combine_inten_a_and_inten_a_row_float; 
    behind_inten_row = behind_inten_row_float; 
    behind_indexed_row = NULL; 
    replace_inten_row = replace_inten_row_float; 
    replace_indexed_row = NULL; 
    erase_inten_row = erase_inten_row_float; 
    erase_indexed_row = NULL;
    break;
  case PRECISION_FLOAT16:
    combine_indexed_and_indexed_row = NULL;
    combine_indexed_and_indexed_a_row = NULL; 
    combine_indexed_a_and_indexed_a_row = NULL;
    combine_inten_a_and_indexed_a_row = NULL; 
    combine_inten_a_and_channel_mask_row = combine_inten_a_and_channel_mask_row_float16; 
    combine_inten_a_and_channel_selection_row = combine_inten_a_and_channel_selection_row_float16; 
    combine_inten_and_inten_row = combine_inten_and_inten_row_float16; 
    combine_inten_and_inten_a_row = combine_inten_and_inten_a_row_float16; 
    combine_inten_a_and_inten_row = combine_inten_a_and_inten_row_float16; 
    combine_inten_a_and_inten_a_row = combine_inten_a_and_inten_a_row_float16; 
    behind_inten_row = behind_inten_row_float16; 
    behind_indexed_row = NULL; 
    replace_inten_row = replace_inten_row_float16; 
    replace_indexed_row = NULL; 
    erase_inten_row = erase_inten_row_float16; 
    erase_indexed_row = NULL;
    break;
  default:
    combine_indexed_and_indexed_row = NULL;
    combine_indexed_and_indexed_a_row = NULL; 
    combine_indexed_a_and_indexed_a_row = NULL;
    combine_inten_a_and_indexed_a_row = NULL; 
    combine_inten_a_and_channel_mask_row = NULL; 
    combine_inten_a_and_channel_selection_row = NULL; 
    combine_inten_and_inten_row = NULL; 
    combine_inten_and_inten_a_row = NULL; 
    combine_inten_a_and_inten_row = NULL; 
    combine_inten_a_and_inten_a_row = NULL; 
    behind_inten_row = NULL; 
    behind_indexed_row = NULL; 
    replace_inten_row = NULL; 
    replace_indexed_row = NULL; 
    erase_inten_row = NULL; 
    erase_indexed_row = NULL;
    break;
  } 
}

int
combine_areas_type (
                    Tag src,
                    Tag dst
                    )
{
  int s_index;
  int d_index;
  static int valid_combos[][6] =
  {
    {
      COMBINE_INTEN_INTEN, COMBINE_INTEN_INTEN_A,
      -1, -1,
      -1, -1
    },
    {
      COMBINE_INTEN_A_INTEN, COMBINE_INTEN_A_INTEN_A,
      -1, -1,
      -1, -1
    },
    {
      -1, -1,
      COMBINE_INTEN_INTEN, COMBINE_INTEN_INTEN_A,
      -1, -1
    },
    {
      -1, -1,
      COMBINE_INTEN_A_INTEN, COMBINE_INTEN_A_INTEN_A,
      -1, -1
    },
    {
      -1, -1,
      -1, -1,
      COMBINE_INDEXED_INDEXED, COMBINE_INDEXED_INDEXED_A
    },
    {
      -1, -1,
      -1, -1,
      -1, COMBINE_INDEXED_A_INDEXED_A
    }
  };

  switch (tag_format (src))
    {
    case FORMAT_RGB:      s_index = 0; break;
    case FORMAT_GRAY:     s_index = 1; break;
    case FORMAT_INDEXED:  s_index = 2; break;
    case FORMAT_NONE:
    default:              return -1;
    }
  
  switch (tag_format (dst))
    {
    case FORMAT_RGB:      d_index = 0; break;
    case FORMAT_GRAY:     d_index = 1; break;
    case FORMAT_INDEXED:  d_index = 2; break;
    case FORMAT_NONE:
    default:              return -1;
    }

  s_index = 2 * s_index + (tag_alpha (src) == ALPHA_YES ? 1 : 0);
  d_index = 2 * d_index + (tag_alpha (dst) == ALPHA_YES ? 1 : 0);

  return valid_combos[s_index][d_index];
  
}

void 
combine_areas  (
                PixelArea * src1_area,
                PixelArea * src2_area,
                PixelArea * dest_area,
                PixelArea * mask_area,
                unsigned char * data,   /* a colormap or a color --if needed */
                gfloat opacity,
                gint mode,
                gint * affect,
                gint type
                )
{
  void *  pag;
  gint combine = type;
  gint mode_affect;
  PixelRow buf_row;
  gint buf_size;
  guchar *buf_row_data = 0; 
  Tag src1_tag = pixelarea_tag (src1_area); 
  Tag src2_tag = pixelarea_tag (src2_area); 
  Tag dest_tag = pixelarea_tag (dest_area); 
  Tag mask_tag = pixelarea_tag (mask_area); 
  Tag buf_tag = src2_tag;
  gint src2_width = pixelarea_areawidth (src2_area);
  gint src2_bytes = tag_bytes (src2_tag);
 
  if ((tag_precision (src1_tag) != tag_precision (src2_tag)) ||
      (tag_precision (src1_tag) != tag_precision (dest_tag)) ||
      (tag_format    (src1_tag) != tag_format    (dest_tag)) ||
      (mask_area &&
       (tag_precision (src1_tag) != tag_precision (mask_tag))))
    {
      g_warning ("combine_areas: bad tags!");
      return;
    }

  combine_areas_funcs (src1_tag); 
  
  buf_size = src2_width * src2_bytes;
  buf_row_data = (guchar *) g_malloc (buf_size);
  pixelrow_init (&buf_row, buf_tag , buf_row_data, src2_width); 
  
  for (pag = pixelarea_register (4, src1_area, src2_area, dest_area, mask_area);
       pag != NULL;
       pag = pixelarea_process (pag))
    {
      PixelRow src1_row;
      PixelRow src2_row;
      PixelRow dest_row;
      PixelRow mask_row;
 
      gint h = pixelarea_height (src1_area);
      gint src1_x = pixelarea_x (src1_area);
      gint src1_y = pixelarea_y (src1_area);
      while (h--)
        {
          pixelarea_getdata (src1_area, &src1_row, h);
          pixelarea_getdata (src2_area, &src2_row, h);
          pixelarea_getdata (dest_area, &dest_row, h);
          pixelarea_getdata (mask_area, &mask_row, h);
           
	  /*  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 (&src1_row, &src2_row, &buf_row, mode);
	      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  */
	      combine = apply_layer_mode (&src1_row, &src2_row, &buf_row, src1_x, src1_y+ h, opacity, mode, &mode_affect);
	      break;

	    default:
	      break;
	    }
	 
  
          /*  based on the type of the initial image...  */
	  switch (combine)
	    {
	    case COMBINE_INDEXED_INDEXED:
              (*combine_indexed_and_indexed_row) (&src1_row, &src2_row, &dest_row, &mask_row, opacity, affect);
	      break;

	    case COMBINE_INDEXED_INDEXED_A:
              (*combine_indexed_and_indexed_a_row) (&src1_row, &src2_row, &dest_row, &mask_row, opacity, affect);
	      break;

	    case COMBINE_INDEXED_A_INDEXED_A:
              (*combine_indexed_a_and_indexed_a_row) (&src1_row, &src2_row, &dest_row, &mask_row, opacity, affect);
	      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_row) (&src1_row, &src2_row, &dest_row, &mask_row, data, opacity );
	      break;

	    case COMBINE_INTEN_A_CHANNEL_MASK:
	      /*  assume the data passed to this procedure is the channels color
	       * 
	       */
              (*combine_inten_a_and_channel_mask_row) (&src1_row, &src2_row, &dest_row, (PixelRow*)data, opacity );
	      break;

	    case COMBINE_INTEN_A_CHANNEL_SELECTION:
              (*combine_inten_a_and_channel_selection_row) (&src1_row, &src2_row, &dest_row, (PixelRow*)data, opacity );
	      break;


	    case COMBINE_INTEN_INTEN:
              (*combine_inten_and_inten_row) (&src1_row, &buf_row, &dest_row, &mask_row, opacity, affect);
	      break;

	    case COMBINE_INTEN_INTEN_A:
              (*combine_inten_and_inten_a_row) (&src1_row, &buf_row, &dest_row, &mask_row, opacity, affect);
	      break;

	    case COMBINE_INTEN_A_INTEN:
              (*combine_inten_a_and_inten_row) (&src1_row, &buf_row, &dest_row, &mask_row, opacity, affect, mode_affect);
	      break;

	    case COMBINE_INTEN_A_INTEN_A:
              (*combine_inten_a_and_inten_a_row) (&src1_row, &buf_row, &dest_row, &mask_row, opacity, affect, mode_affect);
	      break;

	    case BEHIND_INTEN:
              (*behind_inten_row) (&src1_row, &buf_row, &dest_row, &mask_row, opacity, affect);
	      break;

	    case BEHIND_INDEXED:
              (*behind_indexed_row) (&src1_row, &buf_row, &dest_row, &mask_row, opacity, affect);
	      break;

	    case REPLACE_INTEN:
              (*replace_inten_row) (&src1_row, &buf_row, &dest_row, &mask_row, opacity, affect);
	      break;

	    case REPLACE_INDEXED:
              (*replace_indexed_row) (&src1_row, &buf_row, &dest_row, &mask_row, opacity, affect);
	      break;

	    case ERASE_INTEN:
              (*erase_inten_row) (&src1_row, &buf_row, &dest_row, &mask_row, opacity, affect);
	      break;

	    case ERASE_INDEXED:
              (*erase_indexed_row) (&src1_row, &buf_row, &dest_row, &mask_row, opacity, affect);
	      break;

	    case NO_COMBINATION:
	      break;

	    default:
	      break;
	    }
        }
    } 
    if (buf_row_data)
      g_free (buf_row_data);
}


void 
combine_areas_replace  (
                        PixelArea * src1_area,
                        PixelArea * src2_area,
                        PixelArea * dest_area,
                        PixelArea * mask_area,
                        guchar * data,
                        gfloat opacity,
                        gint * affect,
                        gint type
                        )
{
  void *  pag;
  
   /*put in tags check*/
  
  for (pag = pixelarea_register (4, src1_area, src2_area, dest_area, mask_area);
       pag != NULL;
       pag = pixelarea_process (pag))
    {
      PixelRow src1_row;
      PixelRow src2_row;
      PixelRow dest_row;
      PixelRow mask_row;
	
 
      gint h = pixelarea_height (src1_area);
      while (h--)
        {
	  pixelarea_getdata (src1_area, &src1_row, h);
	  pixelarea_getdata (src2_area, &src2_row, h);
	  pixelarea_getdata (dest_area, &dest_row, h);
	  pixelarea_getdata (mask_area, &mask_row, h);
         /*apply_layer_mode_replace (s1, s2, d, m, src1->x, src1->y + h, opacity, src1->w, src1->bytes, src2->bytes, affect);*/
         apply_layer_mode_replace (&src1_row, &src2_row, &dest_row, &mask_row, opacity, affect);
	}
    }
}


/*========================================================================*/

/*
 * The equations: g(r) = exp (- r^2 / (2 * sigma^2))
 *                   r = sqrt (x^2 + y ^2)
 *    return curve is mapping of [-length,length]-->[0.0,1.0]
 */

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

  sigma2 = 2 * sigma * sigma;
  l = sqrt (-sigma2 * log (precision_cutoff));

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

  curve = g_malloc (sizeof (gdouble) * n);

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

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

static void
draw_segments (
	       PixelArea   *dest_area,
	       BoundSeg    *bs,
	       gint          num_segs,
	       gint          off_x,
	       gint          off_y,
	       gfloat opacity
               )
{
  gint x1, y1, x2, y2;
  gint tmp, i, length;
  PixelRow line;
  guchar *line_data;
  Tag dest_tag = pixelarea_tag (dest_area);
  gint width = pixelarea_areawidth (dest_area);
  gint height = pixelarea_areaheight (dest_area);
  gint bytes_per_pixel = tag_bytes (dest_tag); 
  
  length = MAXIMUM (width , height);

  /* get a row with same tag type as dest */
  line_data = (guchar *) g_malloc (length * bytes_per_pixel); 
  pixelrow_init (&line, dest_tag, line_data, length); 
  
  /* initialize line with opacity */
  {
    Tag t = tag_new (PRECISION_FLOAT, FORMAT_GRAY, ALPHA_NO);
    PixelRow paint;
    pixelrow_init (&paint, t, (guchar*)&opacity, 1);
    color_row (&line, &paint);
  }

  for (i = 0; i < num_segs; i++)
    {
      x1 = bs[i].x1 + off_x;
      y1 = bs[i].y1 + off_y;
      x2 = bs[i].x2 + off_x;
      y2 = bs[i].y2 + off_y;

      if (bs[i].open == 0)
	{
	  /*  If it is vertical  */
	  if (x1 == x2)
	    {
	      x1 -= 1;
	      x2 -= 1;
	    }
	  else
	    {
	      y1 -= 1;
	      y2 -= 1;
	    }
	}

      /*  render segment  */
      x1 = BOUNDS (x1, 0, width - 1);
      y1 = BOUNDS (y1, 0, height - 1);
      x2 = BOUNDS (x2, 0, width - 1);
      y2 = BOUNDS (y2, 0, height - 1);

      if (x1 == x2)
	{
	  if (y2 < y1)
	    {
	      tmp = y1;
	      y1 = y2;
	      y2 = tmp;
	    }
	  /*pixel_region_set_col (destPR, x1, y1, (y2 - y1), line);*/
          pixelarea_write_col (dest_area, &line, x1, y1, (y2 - y1)); 
	}
      else
	{
	  if (x2 < x1)
	    {
	      tmp = x1;
	      x1 = x2;
	      x2 = tmp;
	    }
	  /*pixel_region_set_row (destPR, x1, y1, (x2 - x1), line);*/
          pixelarea_write_row (dest_area, &line, x1, y1, (y2 - y1)); 
	}
    }
    g_free( line_data );
}

static gdouble
cubic (
       gdouble  dx,
       gdouble  jm1,
       gdouble  j,
       gdouble  jp1,
       gdouble  jp2,
       gdouble  hi,    /* the hi code for data type */
       gdouble  lo     /* the lo code for data type */
       )
{
  double dx1, dx2, dx3;
  double h1, h2, h3, h4;
  double result;

  /*  constraint parameter = -1  */
  dx1 = fabs (dx);
  dx2 = dx1 * dx1;
  dx3 = dx2 * dx1;
  h1 = dx3 - 2 * dx2 + 1;
  result = h1 * j;

  dx1 = fabs (dx - 1.0);
  dx2 = dx1 * dx1;
  dx3 = dx2 * dx1;
  h2 = dx3 - 2 * dx2 + 1;
  result += h2 * jp1;

  dx1 = fabs (dx - 2.0);
  dx2 = dx1 * dx1;
  dx3 = dx2 * dx1;
  h3 = -dx3 + 5 * dx2 - 8 * dx1 + 4;
  result += h3 * jp2;

  dx1 = fabs (dx + 1.0);
  dx2 = dx1 * dx1;
  dx3 = dx2 * dx1;
  h4 = -dx3 + 5 * dx2 - 8 * dx1 + 4;
  result += h4 * jm1;

  if (result < lo)
    result = lo;
  if (result > hi)
    result = hi;

  return result;
}


/************************************/
/*       apply layer modes          */
/************************************/
static void apply_layer_mode_funcs (Tag);

typedef void (*MultiplyRowFunc) (PixelRow *, PixelRow *, PixelRow *);
typedef void (*ScreenRowFunc) (PixelRow *, PixelRow *, PixelRow *);
typedef void (*OverlayRowFunc) (PixelRow *, PixelRow *, PixelRow *);
typedef void (*DifferenceRowFunc) (PixelRow *, PixelRow *, PixelRow *);
typedef void (*AddRowFunc) (PixelRow *, PixelRow *, PixelRow *);
typedef void (*SubtractRowFunc) (PixelRow *, PixelRow *, PixelRow *);
typedef void (*DarkenRowFunc) (PixelRow *, PixelRow *, PixelRow *);
typedef void (*LightenRowFunc) (PixelRow *, PixelRow *, PixelRow *);
typedef void (*HsvOnlyRowFunc) (PixelRow *, PixelRow *, PixelRow *, gint);
typedef void (*ColorOnlyRowFunc) (PixelRow *, PixelRow *, PixelRow *, gint);
static AddAlphaRowFunc add_alpha_row;
static MultiplyRowFunc multiply_row;
static ScreenRowFunc screen_row;
static OverlayRowFunc overlay_row;
static DifferenceRowFunc difference_row;
static AddRowFunc add_row;
static SubtractRowFunc subtract_row;
static DarkenRowFunc darken_row;
static LightenRowFunc lighten_row;
static HsvOnlyRowFunc hsv_only_row;
static ColorOnlyRowFunc color_only_row;

static void 
apply_layer_mode_funcs (
		Tag tag
		)
	  
{
  switch (tag_precision (tag))
  {
  case PRECISION_U8:
    add_alpha_row = add_alpha_row_u8;
    dissolve_row = dissolve_row_u8; 
    multiply_row = multiply_row_u8; 
    screen_row = screen_row_u8; 
    overlay_row = overlay_row_u8; 
    difference_row = difference_row_u8; 
    add_row = add_row_u8; 
    subtract_row = subtract_row_u8; 
    darken_row = darken_row_u8; 
    lighten_row = lighten_row_u8; 
    hsv_only_row = hsv_only_row_u8; 
    color_only_row = color_only_row_u8; 
    break;
  case PRECISION_U16:
    add_alpha_row = add_alpha_row_u16;
    dissolve_row = dissolve_row_u16; 
    multiply_row = multiply_row_u16; 
    screen_row = screen_row_u16; 
    overlay_row = overlay_row_u16; 
    difference_row = difference_row_u16; 
    add_row = add_row_u16; 
    subtract_row = subtract_row_u16; 
    darken_row = darken_row_u16; 
    lighten_row = lighten_row_u16; 
    hsv_only_row = hsv_only_row_u16; 
    color_only_row = color_only_row_u16; 
    break;
  case PRECISION_FLOAT:
    add_alpha_row = add_alpha_row_float;
    dissolve_row = dissolve_row_float; 
    multiply_row = multiply_row_float; 
    screen_row = screen_row_float; 
    overlay_row = overlay_row_float; 
    difference_row = difference_row_float; 
    add_row = add_row_float; 
    subtract_row = subtract_row_float; 
    darken_row = darken_row_float; 
    lighten_row = lighten_row_float; 
    hsv_only_row = hsv_only_row_float; 
    color_only_row = color_only_row_float; 
    break;
  case PRECISION_FLOAT16:
    add_alpha_row = add_alpha_row_float16;
    dissolve_row = dissolve_row_float16; 
    multiply_row = multiply_row_float16; 
    screen_row = screen_row_float16; 
    overlay_row = overlay_row_float16; 
    difference_row = difference_row_float16; 
    add_row = add_row_float16; 
    subtract_row = subtract_row_float16; 
    darken_row = darken_row_float16; 
    lighten_row = lighten_row_float16; 
    hsv_only_row = hsv_only_row_float16; 
    color_only_row = color_only_row_float16; 
    break;
  default:
    add_alpha_row = NULL;
    dissolve_row = NULL; 
    multiply_row = NULL; 
    screen_row = NULL; 
    overlay_row = NULL; 
    difference_row = NULL; 
    add_row = NULL; 
    subtract_row = NULL; 
    darken_row = NULL; 
    lighten_row = NULL; 
    hsv_only_row = NULL; 
    color_only_row = NULL; 
    break;
  } 
}


static int 
apply_layer_mode  (
                   PixelRow *src1_row,
                   PixelRow *src2_row,
                   PixelRow *dest_row,
                   gint x,
                   gint y,
                   gfloat opacity,
                   gint mode,
                   gint * mode_affect
                   )
{
  gint combine;
  Tag src1_tag = pixelrow_tag (src1_row);
  Tag src2_tag = pixelrow_tag (src2_row);
  Tag dest_tag = pixelrow_tag (dest_row);
  gint ha1  = (tag_alpha (src1_tag)==ALPHA_YES)? TRUE: FALSE;
  gint ha2  = (tag_alpha (src2_tag)==ALPHA_YES)? TRUE: FALSE;
  guchar *src2_data = pixelrow_data (src2_row); 
  gint width = pixelrow_width (dest_row); 
  Format src1_format = tag_format (src1_tag);
  
  apply_layer_mode_funcs (src1_tag);
 
  if (!ha1 && !ha2)
    combine = COMBINE_INTEN_INTEN;
  else if (!ha1 && ha2)
    combine = COMBINE_INTEN_INTEN_A;
  else if (ha1 && !ha2)
    combine = COMBINE_INTEN_A_INTEN;
  else
    combine = COMBINE_INTEN_A_INTEN_A;

  /*  assumes we're applying src2 TO src1  */
  switch (mode)
    {
    case NORMAL_MODE:
      /* *dest = src2; */
      pixelrow_init (dest_row, dest_tag, src2_data, width);
      break;

    case DISSOLVE_MODE:
      /*  Since dissolve requires an alpha channels...  */
      if (! ha2)
	/*add_alpha_pixels (src2, *dest, length, b2);*/
      (*add_alpha_row) (src2_row, dest_row);

      /*dissolve_pixels (src2, *dest, x, y, opacity, length, b2, ((ha2) ? b2 : b2 + 1), ha2);*/
      (*dissolve_row) (src2_row, dest_row, x, y, opacity);
      combine = (ha1) ? COMBINE_INTEN_A_INTEN_A : COMBINE_INTEN_INTEN_A;
      break;

    case MULTIPLY_MODE:
      /*multiply_pixels (src1, src2, *dest, length, b1, b2, ha1, ha2);*/
      (*multiply_row) (src1_row, src2_row, dest_row);
      break;

    case SCREEN_MODE:
      /*screen_pixels (src1, src2, *dest, length, b1, b2, ha1, ha2);*/
      (*screen_row) (src1_row, src2_row, dest_row);
      break;

    case OVERLAY_MODE:
      /*overlay_pixels (src1, src2, *dest, length, b1, b2, ha1, ha2);*/
      (*overlay_row) (src1_row, src2_row, dest_row);
      break;

    case DIFFERENCE_MODE:
      /*difference_pixels (src1, src2, *dest, length, b1, b2, ha1, ha2);*/
      (*difference_row) (src1_row, src2_row, dest_row);
      break;

    case ADDITION_MODE:
      /*add_pixels (src1, src2, *dest, length, b1, b2, ha1, ha2);*/
      (*add_row) (src1_row, src2_row, dest_row);
      break;

    case SUBTRACT_MODE:
      /*subtract_pixels (src1, src2, *dest, length, b1, b2, ha1, ha2);*/
      (*subtract_row) (src1_row, src2_row, dest_row);
      break;

    case DARKEN_ONLY_MODE:
      /*darken_pixels (src1, src2, *dest, length, b1, b2, ha1, ha2);*/
      (*darken_row) (src1_row, src2_row, dest_row);
      break;

    case LIGHTEN_ONLY_MODE:
      /*lighten_pixels (src1, src2, *dest, length, b1, b2, ha1, ha2);*/
      (*lighten_row) (src1_row, src2_row, dest_row);
      break;

    case HUE_MODE: case SATURATION_MODE: case VALUE_MODE:
      /*  only works on RGB color images  */
      /*if (b1 > 2) */
	/*hsv_only_pixels (src1, src2, *dest, mode, length, b1, b2, ha1, ha2);*/
      if( src1_format == FORMAT_RGB )
        (*hsv_only_row) (src1_row, src2_row, dest_row, mode);
      /*else*/
	/* *dest = src2; */
         /*pixelrow_init (dest_row, dest_tag, src2_data, width);
      */break;

    case COLOR_MODE:
      /*  only works on RGB color images  */
      /* if (b1 > 2) */
	 /* color_only_pixels (src1, src2, *dest, mode, length, b1, b2, ha1, ha2); */
      if( src1_format == FORMAT_RGB )
        (*color_only_row) (src1_row, src2_row, dest_row, mode);
      else
         /* *dest = src2; */
         pixelrow_init (dest_row, dest_tag, src2_data, width);
      break;

    case BEHIND_MODE:
      /* *dest = src2; */
      pixelrow_init (dest_row, dest_tag, src2_data, width);
      if (ha1)
	combine = BEHIND_INTEN;
      else
	combine = NO_COMBINATION;
      break;

    case REPLACE_MODE:
      /* *dest = src2; */
      pixelrow_init (dest_row, dest_tag, src2_data, width);
      combine = REPLACE_INTEN;
      break;

    case ERASE_MODE:
      /* *dest = src2; */
      pixelrow_init (dest_row, dest_tag, src2_data, width);
      /*  If both sources have alpha channels, call erase function.
       *  Otherwise, just combine in the normal manner
       */
      combine = (ha1 && ha2) ? ERASE_INTEN : combine;
      break;

    default :
        e_printf ("ERROR: Mode not implemented\n");
      break;
    }

  /*  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_16[mode].affect_alpha;

  return combine;
}


static int 
apply_indexed_layer_mode  (
                           PixelRow * src1_row,
                           PixelRow * src2_row,
                           PixelRow * dest_row,
                           gint mode
                           )
{

  int combine;
  Tag src1_tag = pixelrow_tag (src1_row);
  Tag src2_tag = pixelrow_tag (src2_row);
  Tag dest_tag = pixelrow_tag (dest_row);
  gint ha1  = (tag_alpha (src1_tag)==ALPHA_YES)? TRUE: FALSE;
  gint ha2  = (tag_alpha (src2_tag)==ALPHA_YES)? TRUE: FALSE;
  guchar *src2_data = pixelrow_data (src2_row); 
  gint width = pixelrow_width (dest_row); 

  if (!ha1 && !ha2)
    combine = COMBINE_INDEXED_INDEXED;
  else if (!ha1 && ha2)
    combine = COMBINE_INDEXED_INDEXED_A;
  else if (ha1 && ha2)
    combine = COMBINE_INDEXED_A_INDEXED_A;
  else
    combine = NO_COMBINATION;

  /*  assumes we're applying src2 TO src1  */
  switch (mode)
    {
    case REPLACE_MODE:
      /* make the data of dest point to src2 */
      /* *dest = src2; */
      pixelrow_init (dest_row, dest_tag, src2_data, width);
      combine = REPLACE_INDEXED;
      break;

    case BEHIND_MODE:
      /* make the data of dest point to src2 */
      /* *dest = src2; */
      pixelrow_init (dest_row, dest_tag, src2_data, width);
      if (ha1)
	combine = BEHIND_INDEXED;
      else
	combine = NO_COMBINATION;
      break;

    case ERASE_MODE:
      /* make the data of dest point to src2 */
      /* *dest = src2; */
      pixelrow_init (dest_row, dest_tag, src2_data, width);
      /*  If both sources have alpha channels, call erase function.
       *  Otherwise, just combine in the normal manner
       */
      combine = (ha1 && ha2) ? ERASE_INDEXED : combine;
      break;

    default:
      break;
    }

  return combine;
}


typedef void (*ReplaceRowFunc) (PixelRow*, PixelRow*, PixelRow*, PixelRow*, gfloat, gint*);
static ReplaceRowFunc apply_layer_mode_replace_funcs (Tag);

static ReplaceRowFunc 
apply_layer_mode_replace_funcs (
		Tag tag
		)
	  
{
  switch (tag_precision (tag))
  {
  case PRECISION_U8:
    return replace_row_u8;
  case PRECISION_U16:
    return replace_row_u16;
  case PRECISION_FLOAT:
    return replace_row_float;
  case PRECISION_FLOAT16:
    return replace_row_float16;
  default:
    return NULL;
  } 
}
 
static void 
apply_layer_mode_replace  (
                           PixelRow * src1_row,
                           PixelRow * src2_row,
                           PixelRow * dest_row,
                           PixelRow * mask_row,
                           gfloat opacity,
                           gint * affect
                           )
{
  Tag src1_tag = pixelrow_tag (src1_row);
  ReplaceRowFunc replace_row = apply_layer_mode_replace_funcs (src1_tag);
  /*replace_pixels (src1, src2, dest, mask, length, opacity, affect, b1, b2);*/
  (*replace_row) (src1_row, src2_row, dest_row, mask_row, opacity, affect);
}


/*********************************
 *   color conversion routines   *
 *********************************/
void
rgb_to_hsv (gfloat *r,
    gfloat *g,
    gfloat *b)
{
  gfloat red, green, blue;
  gfloat h, s, v;
  gfloat min, max;
  gfloat delta;

  h = 0.0;

  red = *r;
  green = *g;
  blue = *b;

  max = red;
  max = max < green ? green : max;
  max = max < blue ? blue : max;
  min = red;
  min = min > green ? green : min;
  min = min > blue ? blue : min;

  v = max;

  s = max ? (max - min) / max : max;

  if (s == 0)
    h = 0;
  else
    {
      delta = max - min;
      if (red == max)
	h = (green - blue) / delta;
      else if (green == max)
	h = 2.0 + (blue - red) / delta;
      else if (blue == max)
	h = 4.0 + (red - green) / delta;
      h *= 60.0;

      if (h < 0)
	h += 360;
      if (h > 360)
	h -= 360;
    }

  *r = h;
  *g = s;
  *b = v;
  
}

void
hsv_to_rgb (gfloat *h,
    gfloat *s,
    gfloat *v)

{
  gfloat hue, saturation, value;
  gfloat f, p, q, t;

  if (*s == 0)
    {
      *h = *v;
      *s = *v;
      *v = *v;
    }
  else
    {
      hue = *h; 
      saturation = *s;
      value = *v;

      hue = hue == 360 ? 0 : hue; 
      hue = hue / 60.0;
      f = hue - (int) hue;
      p = value * (1.0 - saturation);
      q = value * (1.0 - (saturation * f));
      t = value * (1.0 - (saturation * (1.0 - f)));

      switch ((int) hue)
	{
	case 0:
	  *h = value;
	  *s = t;
	  *v = p;
	  break;
	case 1:
	  *h = q;
	  *s = value;
	  *v = p;
	  break;
	case 2:
	  *h = p;
	  *s = value;
	  *v = t;
	  break;
	case 3:
	  *h = p;
	  *s = q;
	  *v = value;
	  break;
	case 4:
	  *h = t;
	  *s = p;
	  *v = value;
	  break;
	case 5:
	  *h = value;
	  *s = p;
	  *v = q;
	  break;
	}
    }
}

void
rgb_to_hls (int *r,
	    int *g,
	    int *b)
{
  int red, green, blue;
  float h, l, s;
  int min, max;
  int delta;

  red = *r;
  green = *g;
  blue = *b;

  if (red > green)
    {
      if (red > blue)
	max = red;
      else
	max = blue;

      if (green < blue)
	min = green;
      else
	min = blue;
    }
  else
    {
      if (green > blue)
	max = green;
      else
	max = blue;

      if (red < blue)
	min = red;
      else
	min = blue;
    }

  l = (max + min) / 2.0;

  if (max == min)
    {
      s = 0.0;
      h = 0.0;
    }
  else
    {
      delta = (max - min);

      if (l < 128)
	s = 255 * (float) delta / (float) (max + min);
      else
	s = 255 * (float) delta / (float) (511 - max - min);

      if (red == max)
	h = (green - blue) / (float) delta;
      else if (green == max)
	h = 2 + (blue - red) / (float) delta;
      else
	h = 4 + (red - green) / (float) delta;

      h = h * 42.5;

      if (h < 0)
	h += 255;
      if (h > 255)
	h -= 255;
    }

  *r = h;
  *g = l;
  *b = s;
}


static int
hls_value (float n1,
	   float n2,
	   float hue)
{
  float value;

  if (hue > 255)
    hue -= 255;
  else if (hue < 0)
    hue += 255;
  if (hue < 42.5)
    value = n1 + (n2 - n1) * (hue / 42.5);
  else if (hue < 127.5)
    value = n2;
  else if (hue < 170)
    value = n1 + (n2 - n1) * ((170 - hue) / 42.5);
  else
    value = n1;

  return (int) (value * 255);
}


void
hls_to_rgb (int *h,
	    int *l,
	    int *s)
{
  float hue, lightness, saturation;
  float m1, m2;

  hue = *h;
  lightness = *l;
  saturation = *s;

  if (saturation == 0)
    {
      /*  achromatic case  */
      *h = lightness;
      *l = lightness;
      *s = lightness;
    }
  else
    {
      if (lightness < 128)
	m2 = (lightness * (255 + saturation)) / 65025.0;
      else
	m2 = (lightness + saturation - (lightness * saturation)/255.0) / 255.0;

      m1 = (lightness / 127.5) - m2;

      /*  chromatic case  */
      *h = hls_value (m1, m2, hue + 85);
      *l = hls_value (m1, m2, hue);
      *s = hls_value (m1, m2, hue - 85);
    }
}

