/* @(#)  Functions which takes an initial estimate of deltax, deltay
 * @(#) between reference and secondary images (probably from the scanner),
 * @(#) and looks in three areas of the overlapping part of the reference image
 * @(#) corresponding to reference and secondary.  For every other halfreasize
 * @(#) point of the three areas of the reference image
 * @(#) the contrast is calculated
 * @(#) an area 2*halfcorsize+1 centered at this point
 * @(#) Results are saved in the structure points
 * @(#) The function expects the following valid data in points:
 * @(#) deltax, deltay, nopoints, halfcorsize, halfareasize
 * @(#) and fills in the members:
 * @(#) x, y_reference[], contrast and x,y_secondary[],
 * @(#) based on deltax and deltay
 * @(#) Input image should are either memory mapped or in a buffer.
 * @(#)  The initial setting checks all points of reference
 * @(#) in the overlapping area of the images to be mosaiced
 * @(#)  To speed up the procedure the ysize of the box can be reduced
 * @(#) during the calculation of the ysize
 * @(#) An easy way is to change FACTOR to 1 2 or 3.
 * @(#)  The calculation of the contrast is carried out based on bandno only.
 * @(#) The variable bandno should be between 1 and ref->Bands
 * @(#)
 * @(#) int vips_lrcalcon(ref, sec, bandno, points)
 * @(#) VipsImage *ref, *sec;
 * @(#) int bandno;
 * @(#) TiePoints *points; 	see mosaic.h
 * @(#)
 * @(#) Returns 0 on success  and -1 on error.
 * @(#)
 *
 * Copyright: 1990, N. Dessipris.
 *
 * Author: Nicos Dessipris
 * Written on: 20/12/1990
 * Modified on : 18/04/1991
 * 8/7/93 JC
 *	- now calls im_incheck()
 * 12/7/95 JC
 *	- reworked
 *	- what a lot of horrible old code there was too
 * 24/1/97 JC
 *	- now ignores black stuff (all bands zero) when selecting possible tie
 *	  points, part of new mosaic policy
 * 26/9/97 JC
 *	- now skips all-black windows, instead of any-black windows
 * 11/4/01 JC
 *	- ooops, < 0 should have been <= 0
 * 10/3/03 JC
 *	- better error message for overlap too small
 * 18/6/20 kleisauke
 * 	- convert to vips8
 */

/*

	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 <stdio.h>
#include <stdlib.h>
#include <math.h>

#include <vips/vips.h>

#include "pmosaicing.h"

/* A position and contrast.
 */
typedef struct {
	int x, y;
	int cont;
} PosCont;

/* Search a window for black pelss ... true if window is all black.
 * One-band uchar only.
 */
static int
all_black(VipsImage *im, int xpos, int ypos, int winsize)
{
	const int hwinsize = (winsize - 1) / 2;
	const int left = xpos - hwinsize;
	const int top = ypos - hwinsize;
	const int ls = im->Xsize;

	int x, y;
	VipsPel *line;

	/* Loop over image.
	 */
	line = im->data + top * ls + left;
	for (y = 0; y < winsize; y++) {
		for (x = 0; x < winsize; x++)
			if (line[x])
				/* Not all black.
				 */
				return 0;

		line += ls;
	}

	return -1;
}

/* Calculate a value for 'contrast' within a window
 * of (odd) size winsize*winsize centered at location (xpos, ypos).
 * One band uchar only,
 */
static int
calculate_contrast(VipsImage *im, int xpos, int ypos, int winsize)
{
	const int hwinsize = (winsize - 1) / 2;
	const int left = xpos - hwinsize;
	const int top = ypos - hwinsize;
	const int ls = im->Xsize;

	int x, y;
	VipsPel *line, *p;
	int total;

	line = im->data + top * ls + left;
	for (total = 0, y = 0; y < winsize - 1; y++) {
		p = line;

		for (x = 0; x < winsize - 1; x++) {
			const int lrd = (int) p[0] - p[1];
			const int tbd = (int) p[0] - p[ls];

			total += abs(lrd) + abs(tbd);
			p += 1;
		}

		line += ls;
	}

	return total;
}

/* Compare two PosConts for qsort.
 */
static int
pos_compare(const void *vl, const void *vr)
{
	PosCont *l = (PosCont *) vl;
	PosCont *r = (PosCont *) vr;

	return r->cont - l->cont;
}

/* Search an area for the n best contrast areas.
 */
int
vips__find_best_contrast(VipsImage *im,
	int xpos, int ypos, int xsize, int ysize,
	int xarray[], int yarray[], int cont[],
	int nbest, int hcorsize)
{
	/* Geometry: we test squares of size windowsize, overlapping by
	 * hcorsize.
	 */
	const int windowsize = 2 * hcorsize + 1;

	/* Number of squares we can fit in area.
	 */
	const int nacross = (xsize - windowsize + hcorsize) / hcorsize;
	const int ndown = (ysize - windowsize + hcorsize) / hcorsize;

	/* Number of squares we search.
	 */
	int elms;

	/* All points in this area.
	 */
	PosCont *pc;

	int x, y, i;

	if (nacross <= 0 || ndown <= 0) {
		vips_error("vips__lrcalcon", "%s",
			_("overlap too small for your search size"));
		return -1;
	}

	/* Malloc space for 3 int arrays, to keep the int coordinates and
	 * the contrast.
	 */
	if (!(pc = VIPS_ARRAY(NULL, nacross * ndown, PosCont)))
		return -1;

	/* Find contrast for each area.
	 */
	for (i = 0, y = 0; y < ndown; y++)
		for (x = 0; x < nacross; x++) {
			const int left = xpos + x * hcorsize;
			const int top = ypos + y * hcorsize;

			/* Skip this position if it is all black.
			 */
			if (all_black(im, left, top, windowsize))
				continue;

			/* Find contrast and note.
			 */
			pc[i].x = left;
			pc[i].y = top;
			pc[i].cont = calculate_contrast(im,
				left, top, windowsize);
			i++;
		}

	/* Note number found.
	 */
	elms = i;

	/* Found enough tie-points?
	 */
	if (elms < nbest) {
		vips_error("vips_mosaic",
			_("found %d tie-points, need at least %d"),
			elms, nbest);
		g_free(pc);
		return -1;
	}

	/* Sort areas by contrast.
	 */
	qsort(pc, elms, sizeof(PosCont), pos_compare);

	/* Copy the n best into our parent.
	 */
	for (i = 0; i < nbest; i++) {
		xarray[i] = pc[i].x;
		yarray[i] = pc[i].y;
		cont[i] = pc[i].cont;
	}
	g_free(pc);

	return 0;
}

int
vips__lrcalcon(VipsImage *ref, TiePoints *points)
{
	/* Geometry: border we must leave around each area.
	 */
	const int border = points->halfareasize;

	/* Height of an area.
	 */
	const int aheight = ref->Ysize / AREAS;

	/* Number of points we find in each area.
	 */
	const int len = points->nopoints / AREAS;

	int i;
	VipsRect area;

	/* Make sure we can read image.
	 */
	if (vips_image_wio_input(ref))
		return -1;
	if (ref->Bands != 1 || ref->BandFmt != VIPS_FORMAT_UCHAR) {
		vips_error("vips__lrcalcon", "%s", _("not 1-band uchar image"));
		return -1;
	}

	/* Define bits to search for high-contrast areas. Need to be able to
	 * fit at least 1 window in.
	 */
	area.height = aheight;
	area.width = ref->Xsize;
	area.left = 0;
	area.top = 0;
	vips_rect_marginadjust(&area, -border);
	area.width--;
	area.height--;

	/* Loop over areas, finding points.
	 */
	for (i = 0; area.top < ref->Ysize; area.top += aheight, i++)
		if (vips__find_best_contrast(ref,
				area.left, area.top, area.width, area.height,
				points->x_reference + i * len,
				points->y_reference + i * len,
				points->contrast + i * len,
				len,
				points->halfcorsize))
			return -1;

	return 0;
}
