/*
 *  tiling.c:           Subimage permutation
 *
 *  Written by:         Ullrich Hafner
 *
 *  This file is part of FIASCO (Fractal Image And Sequence COdec)
 *  Copyright (C) 1994-2000 Ullrich Hafner
 */

/*
 *  $Date: 2000/06/14 20:50:51 $
 *  $Author: hafner $
 *  $Revision: 5.1 $
 *  $State: Exp $
 */

#include "config.h"

#include <stdlib.h>

#include "pm_c_util.h"

#include "types.h"
#include "macros.h"
#include "error.h"

#include "image.h"
#include "misc.h"
#include "wfalib.h"
#include "tiling.h"



typedef struct var_list
{
   int    address;                      /* bintree address */
   real_t variance;                     /* variance of tile */
} var_list_t;

#ifndef LITERAL_FN_DEF_MATCH
static qsort_comparison_fn cmpincvar;
#endif

static int
cmpincvar(const void * const value1,
          const void * const value2) {
/*----------------------------------------------------------------------------
  Sorts by increasing variances (quicksort sorting function)
-----------------------------------------------------------------------------*/
    return
        ((var_list_t *) value1)->variance - ((var_list_t *) value2)->variance;
}



#ifndef LITERAL_FN_DEF_MATCH
static qsort_comparison_fn cmpdecvar;
#endif

static int
cmpdecvar(const void * const value1,
          const void * const value2) {
/*----------------------------------------------------------------------------
  Sorts by decreasing variances (quicksort sorting function).
-----------------------------------------------------------------------------*/
    return
        ((var_list_t *) value2)->variance - ((var_list_t *) value1)->variance;
}



tiling_t *
alloc_tiling (fiasco_tiling_e method, unsigned tiling_exponent,
              unsigned image_level)
/*
 *  Image tiling constructor.
 *  Allocate memory for the tiling_t structure.
 *  `method' defines the tiling method (spiral or variance,
 *  ascending or descending).
 *  In case of invalid parameters, a structure with tiling.exponent == 0 is
 *  returned.
 *
 *  Return value
 *      pointer to the new tiling structure on success
 */
{
   tiling_t *tiling = Calloc (1, sizeof (tiling_t));

   if ((int) image_level - (int) tiling_exponent < 6)
   {
      tiling_exponent = 6;
      warning (_("Image tiles must be at least 8x8 pixels large.\n"
                 "Setting tiling size to 8x8 pixels."));
   }

   switch (method)
   {
      case FIASCO_TILING_SPIRAL_ASC:
      case FIASCO_TILING_SPIRAL_DSC:
      case FIASCO_TILING_VARIANCE_ASC:
      case FIASCO_TILING_VARIANCE_DSC:
         tiling_exponent = tiling_exponent;
         break;
      default:
         warning (_("Invalid tiling method specified. Disabling tiling."));
         tiling_exponent = 0;
         break;
   }

   return tiling;
}

void
free_tiling (tiling_t *tiling)
/*
 *  Tiling struct destructor:
 *  Free memory of 'tiling' struct.
 *
 *  No return value.
 *
 *  Side effects:
 *      structure 'tiling' is discarded.
 */
{
   if (tiling->vorder)
      Free (tiling->vorder);
   Free (tiling);
}

void
perform_tiling (const image_t *image, tiling_t *tiling)
/*
 *  Compute image tiling permutation.
 *  The image is split into 2**'tiling->exponent' tiles.
 *  Depending on 'tiling->method', the following algorithms are used:
 *  "VARIANCE_ASC" :  Tiles are sorted by variance.
 *                    The first tile has the lowest variance
 *  "VARIANCE_DSC" :  Tiles are sorted by variance.
 *                    The first tile has the largest variance
 *  "SPIRAL_ASC" :    Tiles are sorted like a spiral starting
 *                    in the middle of the image.
 *  "SPIRAL_DSC" :    Tiles are sorted like a spiral starting
 *                    in the upper left corner.
 *
 *  No return value.
 *
 *  Side effects:
 *      The tiling permutation is stored in 'tiling->vorder'.
 */
{
   if (tiling->exponent)
   {
      unsigned  tiles = 1 << tiling->exponent; /* number of image tiles */
      bool_t   *tile_valid;             /* tile i is in valid range ? */

      tiling->vorder = Calloc (tiles, sizeof (int));
      tile_valid     = Calloc (tiles, sizeof (bool_t));

      if (tiling->method == FIASCO_TILING_VARIANCE_ASC
          || tiling->method == FIASCO_TILING_VARIANCE_DSC)
      {
         unsigned    address;           /* bintree address of tile */
         unsigned    number;            /* number of image tiles */
         unsigned    lx       = log2 (image->width - 1) + 1; /* x level */
         unsigned    ly       = log2 (image->height - 1) + 1; /* y level */
         unsigned    level    = MAX(lx, ly) * 2 - ((ly == lx + 1) ? 1 : 0);
         var_list_t *var_list = Calloc (tiles, sizeof (var_list_t));

         /*
          *  Compute variances of image tiles
          */
         for (number = 0, address = 0; address < tiles; address++)
         {
            unsigned width, height;     /* size of image tile */
            unsigned x0, y0;            /* NW corner of image tile */

            locate_subimage (level, level - tiling->exponent, address,
                             &x0, &y0, &width, &height);
            if (x0 < image->width && y0 < image->height) /* valid range */
            {
               if (x0 + width > image->width)   /* outside image area */
                  width = image->width - x0;
               if (y0 + height > image->height) /* outside image area */
                  height = image->height - y0;

               var_list [number].variance
                  = variance (image->pixels [GRAY], x0, y0,
                              width, height, image->width);
               var_list [number].address  = address;
               number++;
               tile_valid [address] = YES;
            }
            else
               tile_valid [address] = NO;
         }

         /*
          *  Sort image tiles according to sign of 'tiling->exp'
          */
         if (tiling->method == FIASCO_TILING_VARIANCE_DSC)
            qsort (var_list, number, sizeof (var_list_t), cmpdecvar);
         else
            qsort (var_list, number, sizeof (var_list_t), cmpincvar);

         for (number = 0, address = 0; address < tiles; address++)
            if (tile_valid [address])
            {
               tiling->vorder [address] = var_list [number].address;
               number++;
               debug_message ("tile number %d has original address %d",
                              number, tiling->vorder [address]);
            }
            else
               tiling->vorder [address] = -1;

         Free (var_list);
      }
      else if (tiling->method == FIASCO_TILING_SPIRAL_DSC
               || tiling->method == FIASCO_TILING_SPIRAL_ASC)
      {
         compute_spiral (tiling->vorder, image->width, image->height,
                         tiling->exponent,
                         tiling->method == FIASCO_TILING_SPIRAL_ASC);
      }
      else
      {
         warning ("We do not know the tiling method.\n"
                  "Skipping image tiling step.");
         tiling->exponent = 0;
      }
   }
}

