/* im_contrast_surface
 *
 * Copyright: 2006, The Nottingham Trent University
 *
 * Author: Tom Vajzovic
 * (based on algorithm by Nicos Dessipris & John Cupitt)
 *
 * Written on: 2006-03-13
 * 3/2/10
 * 	- gtkdoc
 * 	- small cleanups
 */

/*

    This file is part of VIPS.

    VIPS is free software; you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation; either version 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 Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

 */

/*

    These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk

 */

/** HEADERS **/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H */

#ifdef NOT_IN_VIPS
#define _(s) (s)
#else
#include <vips/intl.h>
#endif

#include <stdlib.h>

#include <vips/vips.h>
#include <vips/region.h>

#ifdef WITH_DMALLOC
#include <dmalloc.h>
#endif /*WITH_DMALLOC */

/** MACROS **/

/* from simple_macros.h */
#define LESSER(a,b)  ((a)<(b)?(a):(b))
#define DOUBLE(a)                ( (a)<<1 )
#define DOUBLE_ADD_ONE(a)  ( 1 | ( (a)<<1 ) )

/** LOCAL TYPES **/

typedef struct cont_surf_params_s
{
  int half_win_size;
  int spacing;

} cont_surf_params_t;

/** LOCAL FUNCTIONS DECLARATIONS **/

int
im_contrast_surface (IMAGE * in, IMAGE * out, int half_win_size, int spacing);

int
im_contrast_surface_raw (IMAGE * in, IMAGE * out, int half_win_size,
			 int spacing);

static int cont_surf_gen (REGION * to_make, void *seq, 
			  void *a, void * b);

static unsigned int calc_cont (REGION * reg, int win_size_less_one,
			       int x_left, int y_top);

/** EXPORTED FUNCTIONS **/

/**
 * im_contrast_surface:
 * @in: input image
 * @out: output image
 * @half_win_size: window radius
 * @spacing: subsample output by this
 *
 * Generate an image where the value of each pixel represents the
 * contrast within a window of half_win_size from the corresponsing
 * point in the input image. Sub-sample by a factor of spacing.
 * 
 * See also: im_spcor(), im_gradcor().
 * 
 * Returns: 0 on success, -1 on error.
 */
int
im_contrast_surface (IMAGE * in, IMAGE * out, int half_win_size, int spacing)
{
  IMAGE *t1 = im_open_local (out, "im_contrast_surface intermediate", "p");

  if (!t1
      || im_embed (in, t1, 1, half_win_size, half_win_size,
		   in->Xsize + DOUBLE (half_win_size),
		   in->Ysize + DOUBLE (half_win_size))
      || im_contrast_surface_raw (t1, out, half_win_size, spacing))

    return -1;

  out->Xoffset = 0;
  out->Yoffset = 0;

  return 0;
}

int
im_contrast_surface_raw (IMAGE * in, IMAGE * out, int half_win_size,
			 int spacing)
{
#define FUNCTION_NAME "im_contrast_surface_raw"

  cont_surf_params_t *params;

  if (im_piocheck (in, out) ||
    im_check_uncoded (FUNCTION_NAME, in) ||
    im_check_mono (FUNCTION_NAME, in) ||
    im_check_format (FUNCTION_NAME, in, IM_BANDFMT_UCHAR))
    return -1;

  if (half_win_size < 1 || spacing < 1)
    {
      im_error (FUNCTION_NAME, "%s", _("bad parameters"));
      return -1;
    }

  if (DOUBLE (half_win_size) >= LESSER (in->Xsize, in->Ysize))
    {
      im_error (FUNCTION_NAME,
		"%s", _("parameters would result in zero size output image"));
      return -1;
    }

  params = IM_NEW (out, cont_surf_params_t);

  if (!params)
    return -1;

  params->half_win_size = half_win_size;
  params->spacing = spacing;

  if (im_cp_desc (out, in))
    return -1;

  out->BandFmt = IM_BANDFMT_UINT;

  out->Xsize = 1 + ((in->Xsize - DOUBLE_ADD_ONE (half_win_size)) / spacing);
  out->Ysize = 1 + ((in->Ysize - DOUBLE_ADD_ONE (half_win_size)) / spacing);

  out->Xoffset = -half_win_size;
  out->Yoffset = -half_win_size;

  if (im_demand_hint (out, IM_FATSTRIP, in, NULL))
    return -1;

  return im_generate (out, im_start_one, cont_surf_gen, im_stop_one, in,
		      params);

#undef FUNCTION_NAME
}

/** LOCAL FUNCTIONS DEFINITIONS **/
static int
cont_surf_gen (REGION * to_make, void * seq, void *unrequired, void * b)
{
  /* I don't need *in, but I will recieve it anyway since im_start_one() needs it */

  REGION * make_from = (REGION *) seq;
  cont_surf_params_t * params = (cont_surf_params_t *) b;

  unsigned int *row =
    (unsigned int *) IM_REGION_ADDR (to_make, to_make->valid.left,
				     to_make->valid.top);
  int xoff;
  int y;
  int bottom = to_make->valid.top + to_make->valid.height;
  size_t lskip = IM_REGION_LSKIP (to_make) / sizeof (unsigned int);

  Rect area = {
    params->spacing * to_make->valid.left,
    params->spacing * to_make->valid.top,
    DOUBLE_ADD_ONE (params->half_win_size) +
      (params->spacing * (to_make->valid.width - 1)),
    DOUBLE_ADD_ONE (params->half_win_size) +
      (params->spacing * (to_make->valid.height - 1))
  };

  if (im_prepare (make_from, &area)
      || !im_rect_equalsrect (&make_from->valid, &area))
    return -1;

  for (y = to_make->valid.top; y < bottom; ++y, row += lskip)

    for (xoff = 0; xoff < to_make->valid.width; ++xoff)

      row[xoff] =
	calc_cont (make_from, DOUBLE (params->half_win_size),
		   (xoff + to_make->valid.left) * params->spacing,
		   y * params->spacing);

  return 0;
}

static unsigned int
calc_cont (REGION * reg, int win_size_less_one, int x_left, int y_top)
{
  unsigned char val;
  unsigned char all_black = 1;
  unsigned char *row;
  unsigned int contrast = 0;
  int xoff;
  int yoff;
  size_t lskip = IM_REGION_LSKIP (reg) / sizeof (unsigned char);

  row = (unsigned char *) IM_REGION_ADDR (reg, x_left, y_top);
  val = *row;

  for (yoff = 0; yoff <= win_size_less_one && all_black; ++yoff, row += lskip)
    for (xoff = 0; xoff <= win_size_less_one; ++xoff)
      if (row[xoff] != val)
	{
	  all_black = 0;
	  break;
	}

  if (all_black)
    return contrast;

  row = (unsigned char *) IM_REGION_ADDR (reg, x_left, y_top);

  for (yoff = 0; yoff < win_size_less_one; ++yoff, row += lskip)
    {
      for (xoff = 0; xoff < win_size_less_one; ++xoff)
	contrast +=
	  abs (row[xoff + 1] - row[xoff]) + abs (row[xoff + lskip] -
						 row[xoff]);

      contrast += abs (row[xoff + lskip] - row[xoff]);
    }

  for (xoff = 0; xoff < win_size_less_one; ++xoff)
    contrast += abs (row[xoff + 1] - row[xoff]);

  return contrast;
}
