/* indexed histogram: use an index image to pick the bins
 *
 * 13/10/09
 * 	- from im_histgr.c
 * 24/3/10
 * 	- gtkdoc
 * 17/8/13
 * 	- redo as a class
 * 2/11/17
 * 	- add @combine ... pick a bin combine mode
 */

/*

	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 <string.h>

#include <vips/vips.h>

#include "statistic.h"

struct _VipsHistFindIndexed;

/* Accumulate a histogram in one of these.
 */
typedef struct {
	struct _VipsHistFindIndexed *indexed;

	VipsRegion *reg; /* Get index pixels with this */

	int size;	  /* Length of bins */
	int mx;		  /* Maximum value we have seen */
	double *bins; /* All the bins! */
	int *init;	  /* TRUE for bin has been initialised */
} Histogram;

typedef struct _VipsHistFindIndexed {
	VipsStatistic parent_instance;

	VipsImage *index;

	/* Index image, cast to uchar/ushort.
	 */
	VipsImage *index_ready;

	/* Main image histogram. Subhists accumulate to this.
	 */
	Histogram *hist;

	/* Write hist to this output image.
	 */
	VipsImage *out;

	/* Combine bins with this.
	 */
	VipsCombine combine;

} VipsHistFindIndexed;

typedef VipsStatisticClass VipsHistFindIndexedClass;

G_DEFINE_TYPE(VipsHistFindIndexed,
	vips_hist_find_indexed, VIPS_TYPE_STATISTIC);

static Histogram *
histogram_new(VipsHistFindIndexed *indexed)
{
	VipsStatistic *statistic = VIPS_STATISTIC(indexed);
	int bands = statistic->ready->Bands;
	Histogram *hist;

	if (!(hist = VIPS_NEW(indexed, Histogram)))
		return NULL;

	hist->indexed = indexed;
	hist->reg = NULL;
	hist->size = indexed->index_ready->BandFmt == VIPS_FORMAT_UCHAR
		? 256
		: 65536;
	hist->mx = 0;
	hist->bins = NULL;
	hist->init = NULL;

	if (!(hist->bins = VIPS_ARRAY(indexed, bands * hist->size, double)) ||
		!(hist->init = VIPS_ARRAY(indexed, hist->size, int)) ||
		!(hist->reg = vips_region_new(indexed->index_ready)))
		return NULL;

	memset(hist->bins, 0, bands * hist->size * sizeof(double));
	memset(hist->init, 0, hist->size * sizeof(int));

	return hist;
}

/* Save a bit of typing.
 */
#define UC VIPS_FORMAT_UCHAR
#define US VIPS_FORMAT_USHORT

/* Type mapping: go to uchar or ushort.
 */
static const VipsBandFormat vips_hist_find_indexed_format[10] = {
	/* Band format:  UC  C   US  S   UI  I   F   X   D   DX */
	/* Promotion: */ UC, UC, US, US, US, US, US, US, US, US
};

static int
vips_hist_find_indexed_build(VipsObject *object)
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS(object);
	VipsStatistic *statistic = VIPS_STATISTIC(object);
	VipsHistFindIndexed *indexed = (VipsHistFindIndexed *) object;
	VipsImage **t = (VipsImage **) vips_object_local_array(object, 2);

	g_object_set(object,
		"out", vips_image_new(),
		NULL);

	/* main hist made on first thread start.
	 */

	/* index image must be cast to uchar/ushort.
	 */
	if (indexed->index &&
		statistic->in) {
		if (vips_check_uncoded(class->nickname, indexed->index) ||
			vips_check_size_same(class->nickname,
				indexed->index, statistic->in) ||
			vips_check_mono(class->nickname, indexed->index))
			return -1;

		if (vips_cast(indexed->index, &t[0],
				vips_hist_find_indexed_format[indexed->index->BandFmt],
				NULL))
			return -1;

		indexed->index_ready = t[0];
	}

	if (statistic->in)
		if (vips_check_noncomplex(class->nickname, statistic->in))
			return -1;

	if (VIPS_OBJECT_CLASS(vips_hist_find_indexed_parent_class)->build(object))
		return -1;

	VIPS_UNREF(indexed->hist->reg);

	if (vips_image_pipelinev(indexed->out,
			VIPS_DEMAND_STYLE_ANY,
			statistic->ready, indexed->index_ready, NULL))
		return -1;
	vips_image_init_fields(indexed->out,
		indexed->hist->mx + 1, 1, statistic->ready->Bands,
		VIPS_FORMAT_DOUBLE, VIPS_CODING_NONE,
		VIPS_INTERPRETATION_HISTOGRAM, 1.0, 1.0);

	if (vips_image_write_line(indexed->out, 0,
			(VipsPel *) indexed->hist->bins))
		return -1;

	return 0;
}

static void *
vips_hist_find_indexed_start(VipsStatistic *statistic)
{
	VipsHistFindIndexed *indexed = (VipsHistFindIndexed *) statistic;

	/* Make the main hist, if necessary.
	 */
	if (!indexed->hist)
		indexed->hist = histogram_new(indexed);

	return (void *) histogram_new(indexed);
}

/* Combine B with A according to mode.
 */
#define COMBINE(MODE, A, B) \
	G_STMT_START \
	{ \
		switch (MODE) { \
		case VIPS_COMBINE_MAX: \
			(A) = VIPS_MAX(A, B); \
			break; \
\
		case VIPS_COMBINE_SUM: \
			(A) += (B); \
			break; \
\
		case VIPS_COMBINE_MIN: \
			(A) = VIPS_MIN(A, B); \
			break; \
\
		default: \
			g_assert_not_reached(); \
		} \
	} \
	G_STMT_END

/* Join a sub-hist onto the main hist.
 */
static int
vips_hist_find_indexed_stop(VipsStatistic *statistic, void *seq)
{
	Histogram *sub_hist = (Histogram *) seq;
	VipsHistFindIndexed *indexed = (VipsHistFindIndexed *) statistic;
	Histogram *hist = indexed->hist;
	int bands = statistic->ready->Bands;

	int i, j;
	double *bins;
	double *sub_bins;
	int *init;
	int *sub_init;

	hist->mx = VIPS_MAX(hist->mx, sub_hist->mx);

	bins = hist->bins;
	sub_bins = sub_hist->bins;
	init = hist->init;
	sub_init = sub_hist->init;
	for (i = 0; i <= sub_hist->mx; i++) {
		if (sub_init[i]) {
			if (init[i])
				for (j = 0; j < bands; j++)
					COMBINE(indexed->combine,
						bins[j], sub_bins[j]);
			else {
				for (j = 0; j < bands; j++)
					bins[j] = sub_bins[j];
				init[i] = TRUE;
			}
		}

		bins += bands;
		sub_bins += bands;
	}

	VIPS_UNREF(sub_hist->reg);

	return 0;
}

/* Accumulate a buffer of pels, uchar index.
 */
#define ACCUMULATE_UCHAR(TYPE) \
	{ \
		int x, z; \
		TYPE *tv = (TYPE *) in; \
\
		for (x = 0; x < n; x++) { \
			int ix = i[x]; \
			double *bin = hist->bins + ix * bands; \
\
			if (hist->init[ix]) \
				for (z = 0; z < bands; z++) \
					COMBINE(indexed->combine, bin[z], tv[z]); \
			else { \
				for (z = 0; z < bands; z++) \
					bin[z] = tv[z]; \
				hist->init[ix] = TRUE; \
			} \
\
			tv += bands; \
		} \
	}

/* A uchar index image.
 */
static void
vips_hist_find_indexed_uchar_scan(VipsHistFindIndexed *indexed,
	Histogram *hist, void *in, void *index, int n)
{
	VipsStatistic *statistic = VIPS_STATISTIC(indexed);
	int bands = statistic->ready->Bands;
	unsigned char *i = (unsigned char *) index;

	switch (statistic->ready->BandFmt) {
	case VIPS_FORMAT_UCHAR:
		ACCUMULATE_UCHAR(unsigned char);
		break;
	case VIPS_FORMAT_CHAR:
		ACCUMULATE_UCHAR(signed char);
		break;
	case VIPS_FORMAT_USHORT:
		ACCUMULATE_UCHAR(unsigned short);
		break;
	case VIPS_FORMAT_SHORT:
		ACCUMULATE_UCHAR(signed short);
		break;
	case VIPS_FORMAT_UINT:
		ACCUMULATE_UCHAR(unsigned int);
		break;
	case VIPS_FORMAT_INT:
		ACCUMULATE_UCHAR(signed int);
		break;
	case VIPS_FORMAT_FLOAT:
		ACCUMULATE_UCHAR(float);
		break;
	case VIPS_FORMAT_DOUBLE:
		ACCUMULATE_UCHAR(double);
		break;

	default:
		g_assert_not_reached();
	}

	/* Max is always 255.
	 */
	hist->mx = 255;
}

/* Accumulate a buffer of pels, ushort index.
 */
#define ACCUMULATE_USHORT(TYPE) \
	{ \
		int x, z; \
		TYPE *tv = (TYPE *) in; \
\
		for (x = 0; x < n; x++) { \
			int ix = i[x]; \
			double *bin = hist->bins + ix * bands; \
\
			if (ix > mx) \
				mx = ix; \
\
			if (hist->init[ix]) \
				for (z = 0; z < bands; z++) \
					COMBINE(indexed->combine, bin[z], tv[z]); \
			else { \
				for (z = 0; z < bands; z++) \
					bin[z] = tv[z]; \
				hist->init[ix] = TRUE; \
			} \
\
			tv += bands; \
		} \
	}

/* A ushort index image.
 */
static void
vips_hist_find_indexed_ushort_scan(VipsHistFindIndexed *indexed,
	Histogram *hist, void *in, void *index, int n)
{
	VipsStatistic *statistic = VIPS_STATISTIC(indexed);
	int bands = statistic->ready->Bands;
	unsigned short *i = (unsigned short *) index;

	int mx;

	mx = hist->mx;

	switch (statistic->ready->BandFmt) {
	case VIPS_FORMAT_UCHAR:
		ACCUMULATE_USHORT(unsigned char);
		break;
	case VIPS_FORMAT_CHAR:
		ACCUMULATE_USHORT(signed char);
		break;
	case VIPS_FORMAT_USHORT:
		ACCUMULATE_USHORT(unsigned short);
		break;
	case VIPS_FORMAT_SHORT:
		ACCUMULATE_USHORT(signed short);
		break;
	case VIPS_FORMAT_UINT:
		ACCUMULATE_USHORT(unsigned int);
		break;
	case VIPS_FORMAT_INT:
		ACCUMULATE_USHORT(signed int);
		break;
	case VIPS_FORMAT_FLOAT:
		ACCUMULATE_USHORT(float);
		break;
	case VIPS_FORMAT_DOUBLE:
		ACCUMULATE_USHORT(double);
		break;

	default:
		g_assert_not_reached();
	}

	/* Note the maximum.
	 */
	hist->mx = mx;
}

typedef void (*VipsHistFindIndexedScanFn)(VipsHistFindIndexed *indexed,
	Histogram *hist, void *in, void *index, int n);

static int
vips_hist_find_indexed_scan(VipsStatistic *statistic, void *seq,
	int x, int y, void *in, int n)
{
	Histogram *hist = (Histogram *) seq;
	VipsHistFindIndexed *indexed = (VipsHistFindIndexed *) statistic;

	VipsRect r = { x, y, n, 1 };
	VipsHistFindIndexedScanFn scan;

	/* Need the corresponding area of the index image.
	 */
	if (vips_region_prepare(hist->reg, &r))
		return -1;

	if (indexed->index_ready->BandFmt == VIPS_FORMAT_UCHAR)
		scan = vips_hist_find_indexed_uchar_scan;
	else
		scan = vips_hist_find_indexed_ushort_scan;

	scan(indexed, hist, in, VIPS_REGION_ADDR(hist->reg, x, y), n);

	return 0;
}

static void
vips_hist_find_indexed_class_init(VipsHistFindIndexedClass *class)
{
	GObjectClass *gobject_class = (GObjectClass *) class;
	VipsObjectClass *object_class = (VipsObjectClass *) class;
	VipsStatisticClass *sclass = VIPS_STATISTIC_CLASS(class);

	gobject_class->set_property = vips_object_set_property;
	gobject_class->get_property = vips_object_get_property;

	object_class->nickname = "hist_find_indexed";
	object_class->description = _("find indexed image histogram");
	object_class->build = vips_hist_find_indexed_build;

	sclass->start = vips_hist_find_indexed_start;
	sclass->scan = vips_hist_find_indexed_scan;
	sclass->stop = vips_hist_find_indexed_stop;

	VIPS_ARG_IMAGE(class, "index", 90,
		_("Index"),
		_("Index image"),
		VIPS_ARGUMENT_REQUIRED_INPUT,
		G_STRUCT_OFFSET(VipsHistFindIndexed, index));

	VIPS_ARG_IMAGE(class, "out", 100,
		_("Output"),
		_("Output histogram"),
		VIPS_ARGUMENT_REQUIRED_OUTPUT,
		G_STRUCT_OFFSET(VipsHistFindIndexed, out));

	VIPS_ARG_ENUM(class, "combine", 104,
		_("Combine"),
		_("Combine bins like this"),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET(VipsHistFindIndexed, combine),
		VIPS_TYPE_COMBINE, VIPS_COMBINE_SUM);
}

static void
vips_hist_find_indexed_init(VipsHistFindIndexed *indexed)
{
	indexed->combine = VIPS_COMBINE_SUM;
}

/**
 * vips_hist_find_indexed: (method)
 * @in: input #VipsImage
 * @index: input index #VipsImage
 * @out: (out): output image
 * @...: %NULL-terminated list of optional named arguments
 *
 * Optional arguments:
 *
 * * @combine: #VipsCombine, combine bins like this
 *
 * Make a histogram of @in, but use image @index to pick the bins. In other
 * words, element zero in @out contains the combination of all the pixels in @in
 * whose corresponding pixel in @index is zero.
 *
 * char and uchar @index images are cast to uchar before histogramming, all
 * other image types are cast to ushort. @index must have just one band.
 *
 * @in must be non-complex.
 *
 * @out always has the same size and format as @in.
 *
 * Normally, bins are summed, but you can use @combine to set other combine
 * modes.
 *
 * This operation is useful in conjunction with vips_labelregions(). You can
 * use it to find the centre of gravity of blobs in an image, for example.
 *
 * See also: vips_hist_find(), vips_labelregions().
 *
 * Returns: 0 on success, -1 on error
 */
int
vips_hist_find_indexed(VipsImage *in, VipsImage *index, VipsImage **out, ...)
{
	va_list ap;
	int result;

	va_start(ap, out);
	result = vips_call_split("hist_find_indexed", ap, in, index, out);
	va_end(ap);

	return result;
}
