/* im_gradcor
 *
 * Copyright: 2007 Nottingham Trent University
 *
 * Author: Tom Vajzovic
 * Written on: 2007-06-07
 * 3/2/10
 * 	- gtkdoc
 * 	- 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., 51 Franklin Street, Fifth Floor, Boston, MA
	02110-1301  USA

 */

/*

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

 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <glib/gi18n-lib.h>

#include <stdlib.h>

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

typedef struct {
	REGION *reg;
	int *region_xgrad;
	int *region_ygrad;
	size_t region_xgrad_area;
	size_t region_ygrad_area;
} gradcor_seq_t;

static void *gradcor_start(IMAGE *out, void *vptr_large, void *unrequired);
static int gradcor_stop(void *vptr_seq, void *unrequired, void *unreq2);
static int gradcor_gen(REGION *to_make, void *vptr_seq, void *unrequired, void *vptr_grads);

#define XGRAD_GEN_DECLARATION(TYPE) static int xgrad_gen_##TYPE(REGION *to_make, void *vptr_make_from, void *unrequired, void *unreq2)
#define YGRAD_GEN_DECLARATION(TYPE) static int ygrad_gen_##TYPE(REGION *to_make, void *vptr_make_from, void *unrequired, void *unreq2)

XGRAD_GEN_DECLARATION(guint8);
YGRAD_GEN_DECLARATION(guint8);
XGRAD_GEN_DECLARATION(gint8);
YGRAD_GEN_DECLARATION(gint8);
XGRAD_GEN_DECLARATION(guint16);
YGRAD_GEN_DECLARATION(guint16);
XGRAD_GEN_DECLARATION(gint16);
YGRAD_GEN_DECLARATION(gint16);
XGRAD_GEN_DECLARATION(guint32);
YGRAD_GEN_DECLARATION(guint32);
XGRAD_GEN_DECLARATION(gint32);
YGRAD_GEN_DECLARATION(gint32);
#if 0
XGRAD_GEN_DECLARATION(float);
YGRAD_GEN_DECLARATION(float);
XGRAD_GEN_DECLARATION(double);
YGRAD_GEN_DECLARATION(double);
#endif

int
im_gradcor_raw(IMAGE *large, IMAGE *small, IMAGE *out)
{
#define FUNCTION_NAME "im_gradcor_raw"

	if (im_piocheck(large, out) || im_pincheck(small))
		return -1;

	if (im_check_uncoded("im_gradcor", large) ||
		im_check_mono("im_gradcor", large) ||
		im_check_uncoded("im_gradcor", small) ||
		im_check_mono("im_gradcor", small) ||
		im_check_format_same("im_gradcor", large, small) ||
		im_check_int("im_gradcor", large))
		return -1;

	if (large->Xsize < small->Xsize || large->Ysize < small->Ysize) {
		im_error(FUNCTION_NAME, "second image must be smaller than first");
		return -1;
	}
	if (im_cp_desc(out, large))
		return -1;

	out->Xsize = 1 + large->Xsize - small->Xsize;
	out->Ysize = 1 + large->Ysize - small->Ysize;
	out->BandFmt = IM_BANDFMT_FLOAT;

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

	{
		IMAGE *xgrad = im_open_local(out, FUNCTION_NAME ": xgrad", "t");
		IMAGE *ygrad = im_open_local(out, FUNCTION_NAME ": ygrad", "t");
		IMAGE **grads = im_allocate_input_array(out, xgrad, ygrad, NULL);

		if (!xgrad ||
			!ygrad ||
			!grads ||
			im_grad_x(small, xgrad) ||
			im_grad_y(small, ygrad))
			return -1;

		if (im_generate(out,
				gradcor_start, gradcor_gen, gradcor_stop, (void *) large, (void *) grads))
			return -1;

		return 0;
	}
#undef FUNCTION_NAME
}

/**
 * im_gradcor:
 * @in: input image
 * @ref: reference image
 * @out: output image
 *
 * Calculate a correlation surface.
 *
 * @ref is placed at every position in @in and a correlation coefficient
 * calculated. One-band, integer images only. @in and @ref must have the
 * same #VipsBandFmt. The output
 * image is always %IM_BANDFMT_FLOAT. @ref must be smaller than @in. The output
 * image is the same size as the input.
 *
 * The method takes the gradient images of the two images then takes the
 * dot-product correlation of the two vector images.
 * The vector expression of this method is my (tcv) own creation. It is
 * equivalent to the complex-number method of:
 *
 * ARGYRIOU, V. et al. 2003. Estimation of sub-pixel motion using
 * gradient cross correlation. Electronics Letters, 39 (13).
 *
 * See also: im_spcor().
 *
 * Returns: 0 on success, -1 on error
 */
int
im_gradcor(IMAGE *in, IMAGE *ref, IMAGE *out)
{
#define FUNCTION_NAME "im_gradcor"
	IMAGE *t1 = im_open_local(out, FUNCTION_NAME " intermediate", "p");

	if (!t1 ||
		im_embed(in, t1, 1,
			ref->Xsize / 2, ref->Ysize / 2,
			in->Xsize + ref->Xsize - 1,
			in->Ysize + ref->Ysize - 1) ||
		im_gradcor_raw(t1, ref, out))
		return -1;

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

	return 0;
#undef FUNCTION_NAME
}

/**
 * im_grad_x:
 * @in: input image
 * @out: output image
 *
 * Find horizontal differences between adjacent pixels.
 *
 * Generates an image where the value of each pixel is the difference between
 * it and the pixel to its right. The output has the same height as the input
 * and one pixel less width. One-band integer formats only. The result is
 * always %IM_BANDFMT_INT.
 *
 * This operation is much faster than (though equivalent to) im_conv() with the
 * mask [[-1, 1]].
 *
 * See also: im_grad_y(), im_conv().
 *
 * Returns: 0 on success, -1 on error
 */
int
im_grad_x(IMAGE *in, IMAGE *out)
{
#define FUNCTION_NAME "im_grad_x"

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

	if (im_check_uncoded("im_grad_x", in) ||
		im_check_mono("im_grad_x", in) ||
		im_check_int("im_grad_x", in))
		return -1;
	if (im_cp_desc(out, in))
		return -1;

	--out->Xsize;
	out->BandFmt = IM_BANDFMT_INT; /* do not change without updating im_gradcor() */

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

#define RETURN_GENERATE(TYPE) return im_generate(out, im_start_one, xgrad_gen_##TYPE, im_stop_one, (void *) in, NULL)

	switch (in->BandFmt) {

	case IM_BANDFMT_UCHAR:
		RETURN_GENERATE(guint8);

	case IM_BANDFMT_CHAR:
		RETURN_GENERATE(gint8);

	case IM_BANDFMT_USHORT:
		RETURN_GENERATE(guint16);

	case IM_BANDFMT_SHORT:
		RETURN_GENERATE(gint16);

	case IM_BANDFMT_UINT:
		RETURN_GENERATE(guint32);

	case IM_BANDFMT_INT:
		RETURN_GENERATE(gint32);
#if 0
    case IM_BANDFMT_FLOAT:
		RETURN_GENERATE(float);
    case IM_BANDFMT_DOUBLE:
		RETURN_GENERATE(double);
#endif
#undef RETURN_GENERATE
	default:
		g_assert(0);
	}

	/* Keep gcc happy.
	 */
	return 0;
#undef FUNCTION_NAME
}

/**
 * im_grad_y:
 * @in: input image
 * @out: output image
 *
 * Find vertical differences between adjacent pixels.
 *
 * Generates an image where the value of each pixel is the difference between
 * it and the pixel below it. The output has the same width as the input
 * and one pixel less height. One-band integer formats only. The result is
 * always %IM_BANDFMT_INT.
 *
 * This operation is much faster than (though equivalent to) im_conv() with the
 * mask [[-1], [1]].
 *
 * See also: im_grad_x(), im_conv().
 *
 * Returns: 0 on success, -1 on error
 */
int
im_grad_y(IMAGE *in, IMAGE *out)
{
#define FUNCTION_NAME "im_grad_y"

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

	if (im_check_uncoded("im_grad_y", in) ||
		im_check_mono("im_grad_y", in) ||
		im_check_int("im_grad_y", in))
		return -1;

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

	--out->Ysize;
	out->BandFmt = IM_BANDFMT_INT; /* do not change without updating im_gradcor() */

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

#define RETURN_GENERATE(TYPE) return im_generate(out, im_start_one, ygrad_gen_##TYPE, im_stop_one, (void *) in, NULL)

	switch (in->BandFmt) {

	case IM_BANDFMT_UCHAR:
		RETURN_GENERATE(guint8);

	case IM_BANDFMT_CHAR:
		RETURN_GENERATE(gint8);

	case IM_BANDFMT_USHORT:
		RETURN_GENERATE(guint16);

	case IM_BANDFMT_SHORT:
		RETURN_GENERATE(gint16);

	case IM_BANDFMT_UINT:
		RETURN_GENERATE(guint32);

	case IM_BANDFMT_INT:
		RETURN_GENERATE(gint32);
#if 0
    case IM_BANDFMT_FLOAT:
		RETURN_GENERATE(float);
    case IM_BANDFMT_DOUBLE:
		RETURN_GENERATE(double);
#endif
#undef RETURN_GENERATE
	default:
		g_assert(0);
	}

	/* Keep gcc happy.
	 */
	return 0;
#undef FUNCTION_NAME
}

static void *
gradcor_start(IMAGE *out, void *vptr_large, void *unrequired)
{

	gradcor_seq_t *seq = IM_NEW(NULL, gradcor_seq_t);
	if (!seq)
		return NULL;

	seq->region_xgrad = (int *) NULL;
	seq->region_ygrad = (int *) NULL;
	seq->region_xgrad_area = 0;
	seq->region_ygrad_area = 0;

	seq->reg = im_region_create((IMAGE *) vptr_large);
	if (!seq->reg) {
		im_free((void *) seq);
		return NULL;
	}
	return (void *) seq;
}

static int
gradcor_stop(void *vptr_seq, void *unrequired, void *unreq2)
{

	gradcor_seq_t *seq = (gradcor_seq_t *) vptr_seq;
	if (seq) {
		im_free((void *) seq->region_xgrad);
		im_free((void *) seq->region_ygrad);
		im_region_free(seq->reg);
		seq->region_xgrad = (int *) NULL;
		seq->region_ygrad = (int *) NULL;
		seq->reg = (REGION *) NULL;
		im_free((void *) seq);
	}
	return 0;
}

static int
gradcor_gen(REGION *to_make, void *vptr_seq, void *unrequired, void *vptr_grads)
{

	gradcor_seq_t *seq = (gradcor_seq_t *) vptr_seq;
	REGION *make_from = seq->reg;

	IMAGE **grads = (IMAGE **) vptr_grads;
	IMAGE *small_xgrad = grads[0];
	IMAGE *small_ygrad = grads[1];

	Rect require = {
		to_make->valid.left,
		to_make->valid.top,
		to_make->valid.width + small_xgrad->Xsize,
		to_make->valid.height + small_ygrad->Ysize
	};
	size_t region_xgrad_width = require.width - 1;
	size_t region_ygrad_height = require.height - 1;

	if (im_prepare(make_from, &require))
		return -1;

#define FILL_BUFFERS(TYPE) /* fill region_xgrad */ \
	{ \
		TYPE *reading = (TYPE *) IM_REGION_ADDR(make_from, require.left, require.top); \
		size_t read_skip = (IM_REGION_LSKIP(make_from) / sizeof(TYPE)) - region_xgrad_width; \
		size_t area_need = region_xgrad_width * require.height; \
\
		if (seq->region_xgrad_area < area_need) { \
			free(seq->region_xgrad); \
			seq->region_xgrad = malloc(area_need * sizeof(int)); \
			if (!seq->region_xgrad) \
				return -1; \
			seq->region_xgrad_area = area_need; \
		} \
		{ \
			int *writing = seq->region_xgrad; \
			int *write_end = writing + area_need; \
			int *write_stop; \
			for (; writing < write_end; reading += read_skip) \
				for (write_stop = writing + region_xgrad_width; writing < write_stop; ++reading, ++writing) \
					*writing = reading[1] - reading[0]; \
		} \
	} \
	{ /* fill region_ygrad */ \
		TYPE *reading = (TYPE *) IM_REGION_ADDR(make_from, require.left, require.top); \
		size_t read_line = IM_REGION_LSKIP(make_from) / sizeof(TYPE); \
		size_t read_skip = read_line - require.width; \
		size_t area_need = require.width * region_ygrad_height; \
\
		if (seq->region_ygrad_area < area_need) { \
			free(seq->region_ygrad); \
			seq->region_ygrad = malloc(area_need * sizeof(int)); \
			if (!seq->region_ygrad) \
				return -1; \
			seq->region_ygrad_area = area_need; \
		} \
		{ \
			int *writing = seq->region_ygrad; \
			int *write_end = writing + area_need; \
			int *write_stop; \
			for (; writing < write_end; reading += read_skip) \
				for (write_stop = writing + require.width; writing < write_stop; ++reading, ++writing) \
					*writing = reading[read_line] - reading[0]; \
		} \
	}
	switch (make_from->im->BandFmt) {
	case IM_BANDFMT_UCHAR:
		FILL_BUFFERS(unsigned char)
		break;
	case IM_BANDFMT_CHAR:
		FILL_BUFFERS(signed char)
		break;
	case IM_BANDFMT_USHORT:
		FILL_BUFFERS(unsigned short int)
		break;
	case IM_BANDFMT_SHORT:
		FILL_BUFFERS(signed short int)
		break;
	case IM_BANDFMT_UINT:
		FILL_BUFFERS(unsigned int)
		break;
	case IM_BANDFMT_INT:
		FILL_BUFFERS(signed int)
		break;
	default:
		g_assert(0);
	}
	{ /* write to output */
		size_t write_skip = IM_REGION_LSKIP(to_make) / sizeof(float);
		float *writing = (float *) IM_REGION_ADDR_TOPLEFT(to_make);
		float *write_end = writing + write_skip * to_make->valid.height;
		float *write_stop;
		size_t write_width = to_make->valid.width;

		size_t small_xgrad_width = small_xgrad->Xsize;
		size_t small_ygrad_width = small_ygrad->Xsize;
		int *small_xgrad_end = (int *) small_xgrad->data + small_xgrad_width * small_xgrad->Ysize;
		int *small_ygrad_end = (int *) small_ygrad->data + small_ygrad_width * small_ygrad->Ysize;

		int *region_xgrad_start = seq->region_xgrad;
		int *region_ygrad_start = seq->region_ygrad;
		size_t region_xgrad_start_skip = region_xgrad_width - write_width;
		size_t region_ygrad_start_skip = require.width - write_width;

		size_t region_xgrad_read_skip = region_xgrad_width - small_xgrad_width;
		size_t region_ygrad_read_skip = require.width - small_ygrad_width;

		write_skip -= write_width;

		for (; writing < write_end; writing += write_skip, region_xgrad_start += region_xgrad_start_skip, region_ygrad_start += region_ygrad_start_skip)
			for (write_stop = writing + write_width; writing < write_stop; ++writing, ++region_xgrad_start, ++region_ygrad_start) {
				gint64 sum = 0;
				{
					int *small_xgrad_read = (int *) small_xgrad->data;
					int *small_xgrad_stop;
					int *region_xgrad_read = region_xgrad_start;

					for (; small_xgrad_read < small_xgrad_end; region_xgrad_read += region_xgrad_read_skip)
						for (small_xgrad_stop = small_xgrad_read + small_xgrad_width; small_xgrad_read < small_xgrad_stop; ++small_xgrad_read, ++region_xgrad_read)
							sum += *small_xgrad_read * *region_xgrad_read;
				}
				{
					int *small_ygrad_read = (int *) small_ygrad->data;
					int *small_ygrad_stop;
					int *region_ygrad_read = region_ygrad_start;

					for (; small_ygrad_read < small_ygrad_end; region_ygrad_read += region_ygrad_read_skip)
						for (small_ygrad_stop = small_ygrad_read + small_ygrad_width; small_ygrad_read < small_ygrad_stop; ++small_ygrad_read, ++region_ygrad_read)
							sum += *small_ygrad_read * *region_ygrad_read;
				}
				*writing = sum;
			}
	}
	return 0;
}

#define XGRAD_GEN_DEFINITION(TYPE) \
	static int xgrad_gen_##TYPE(REGION *to_make, void *vptr_make_from, void *unrequired, void *unreq2) \
	{ \
\
		REGION *make_from = (REGION *) vptr_make_from; \
		Rect require = { \
			to_make->valid.left, \
			to_make->valid.top, \
			to_make->valid.width + 1, \
			to_make->valid.height \
		}; \
		if (im_prepare(make_from, &require)) \
			return -1; \
\
		{ \
			int *writing = (int *) IM_REGION_ADDR_TOPLEFT(to_make); \
			size_t write_skip = IM_REGION_LSKIP(to_make) / sizeof(int); \
			int *write_end = writing + write_skip * to_make->valid.height; \
			size_t write_width = to_make->valid.width; \
			int *write_stop; \
\
			TYPE *reading = (TYPE *) IM_REGION_ADDR(make_from, require.left, require.top); \
			size_t read_skip = (IM_REGION_LSKIP(make_from) / sizeof(TYPE)) - write_width; \
\
			write_skip -= write_width; \
\
			for (; writing < write_end; writing += write_skip, reading += read_skip) \
				for (write_stop = writing + write_width; writing < write_stop; ++writing, ++reading) \
					*writing = (int) (reading[1] - reading[0]); \
		} \
		return 0; \
	}

#define YGRAD_GEN_DEFINITION(TYPE) \
	static int ygrad_gen_##TYPE(REGION *to_make, void *vptr_make_from, void *unrequired, void *unreq2) \
	{ \
\
		REGION *make_from = (REGION *) vptr_make_from; \
		Rect require = { \
			to_make->valid.left, \
			to_make->valid.top, \
			to_make->valid.width, \
			to_make->valid.height + 1 \
		}; \
		if (im_prepare(make_from, &require)) \
			return -1; \
\
		{ \
			int *writing = (int *) IM_REGION_ADDR_TOPLEFT(to_make); \
			size_t write_skip = IM_REGION_LSKIP(to_make) / sizeof(int); \
			int *write_end = writing + write_skip * to_make->valid.height; \
			size_t write_width = to_make->valid.width; \
			int *write_stop; \
\
			TYPE *reading = (TYPE *) IM_REGION_ADDR(make_from, require.left, require.top); \
			size_t read_line = IM_REGION_LSKIP(make_from) / sizeof(TYPE); \
			size_t read_skip = read_line - write_width; \
\
			write_skip -= write_width; \
\
			for (; writing < write_end; writing += write_skip, reading += read_skip) \
				for (write_stop = writing + write_width; writing < write_stop; ++writing, ++reading) \
					*writing = (int) (reading[read_line] - reading[0]); \
		} \
		return 0; \
	}

XGRAD_GEN_DEFINITION(guint8)
YGRAD_GEN_DEFINITION(guint8)
XGRAD_GEN_DEFINITION(gint8)
YGRAD_GEN_DEFINITION(gint8)
XGRAD_GEN_DEFINITION(guint16)
YGRAD_GEN_DEFINITION(guint16)
XGRAD_GEN_DEFINITION(gint16)
YGRAD_GEN_DEFINITION(gint16)
XGRAD_GEN_DEFINITION(guint32)
YGRAD_GEN_DEFINITION(guint32)
XGRAD_GEN_DEFINITION(gint32)
YGRAD_GEN_DEFINITION(gint32)
#if 0
XGRAD_GEN_DEFINITION(float)
YGRAD_GEN_DEFINITION(float)
XGRAD_GEN_DEFINITION(double)
YGRAD_GEN_DEFINITION(double)
#endif
