/* convolution
 *
 * 12/8/13
 * 	- from vips_hist_cum()
 * 8/5/17
 * 	- default to float ... int will often lose precision and should not be
 * 	  the default
 */

/*

	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 <vips/vips.h>
#include <vips/internal.h>

#include "pconvolution.h"

typedef struct {
	VipsConvolution parent_instance;

	VipsPrecision precision;
	int layers;
	int cluster;
} VipsConv;

typedef VipsConvolutionClass VipsConvClass;

G_DEFINE_TYPE(VipsConv, vips_conv, VIPS_TYPE_CONVOLUTION);

static int
vips_conv_build(VipsObject *object)
{
	VipsConvolution *convolution = (VipsConvolution *) object;
	VipsConv *conv = (VipsConv *) object;
	VipsImage **t = (VipsImage **) vips_object_local_array(object, 4);

	VipsImage *in;

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

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

	in = convolution->in;

	/*
	printf("vips_conv_build: convolving with:\n");
	vips_matrixprint(convolution->M, NULL);
	 */

	/* Unpack for processing.
	 */
	if (vips_image_decode(in, &t[0]))
		return -1;
	in = t[0];

	switch (conv->precision) {
	case VIPS_PRECISION_FLOAT:
		if (vips_convf(in, &t[1], convolution->M, NULL) ||
			vips_image_write(t[1], convolution->out))
			return -1;
		break;

	case VIPS_PRECISION_INTEGER:
		if (vips_convi(in, &t[1], convolution->M, NULL) ||
			vips_image_write(t[1], convolution->out))
			return -1;
		break;

	case VIPS_PRECISION_APPROXIMATE:
		if (vips_conva(in, &t[1], convolution->M,
				"layers", conv->layers,
				"cluster", conv->cluster,
				NULL) ||
			vips_image_write(t[1], convolution->out))
			return -1;
		break;

	default:
		g_assert_not_reached();
	}

	vips_reorder_margin_hint(convolution->out,
		convolution->M->Xsize * convolution->M->Ysize);

	return 0;
}

static void
vips_conv_class_init(VipsConvClass *class)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS(class);
	VipsObjectClass *object_class = (VipsObjectClass *) class;

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

	object_class->nickname = "conv";
	object_class->description = _("convolution operation");
	object_class->build = vips_conv_build;

	VIPS_ARG_ENUM(class, "precision", 103,
		_("Precision"),
		_("Convolve with this precision"),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET(VipsConv, precision),
		VIPS_TYPE_PRECISION, VIPS_PRECISION_FLOAT);

	VIPS_ARG_INT(class, "layers", 104,
		_("Layers"),
		_("Use this many layers in approximation"),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET(VipsConv, layers),
		1, 1000, 5);

	VIPS_ARG_INT(class, "cluster", 105,
		_("Cluster"),
		_("Cluster lines closer than this in approximation"),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET(VipsConv, cluster),
		1, 100, 1);
}

static void
vips_conv_init(VipsConv *conv)
{
	conv->precision = VIPS_PRECISION_FLOAT;
	conv->layers = 5;
	conv->cluster = 1;
}

/**
 * vips_conv: (method)
 * @in: input image
 * @out: (out): output image
 * @mask: convolve with this mask
 * @...: `NULL`-terminated list of optional named arguments
 *
 * Perform a convolution of @in with @mask.
 *
 * Each output pixel is calculated as:
 *
 * ```
 * sigma[i]{pixel[i] * mask[i]} / scale + offset
 * ```
 *
 * where scale and offset are part of @mask.
 *
 * By default, @precision is
 * [enum@Vips.Precision.FLOAT]. The output image
 * is always [enum@Vips.BandFormat.FLOAT] unless @in is
 * [enum@Vips.BandFormat.DOUBLE], in which case @out is also
 * [enum@Vips.BandFormat.DOUBLE].
 *
 * If @precision is [enum@Vips.Precision.INTEGER], then elements of @mask
 * are converted to integers before convolution, using `rint()`,
 * and the output image always has the same [enum@BandFormat] as the input
 * image.
 *
 * For [enum@Vips.BandFormat.UCHAR] images and [enum@Vips.Precision.INTEGER]
 * @precision, [method@Image.conv] uses a fast vector path based on
 * half-float arithmetic. This can produce slightly different results.
 * Disable the vector path with `--vips-novector` or `VIPS_NOVECTOR` or
 * [func@vector_set_enabled].
 *
 * If @precision is [enum@Vips.Precision.APPROXIMATE] then, like
 * [enum@Vips.Precision.INTEGER], @mask is converted to int before
 * convolution, and the output image
 * always has the same [enum@BandFormat] as the input image.
 *
 * Larger values for @layers give more accurate
 * results, but are slower. As @layers approaches the mask radius, the
 * accuracy will become close to exact convolution and the speed will drop to
 * match. For many large masks, such as Gaussian, @n_layers need be only 10% of
 * this value and accuracy will still be good.
 *
 * Smaller values of @cluster will give more accurate results, but be slower
 * and use more memory. 10% of the mask radius is a good rule of thumb.
 *
 * ::: tip "Optional arguments"
 *     * @precision: [enum@Precision], calculation accuracy
 *     * @layers: `gint`, number of layers for approximation
 *     * @cluster: `gint`, cluster lines closer than this distance
 *
 * ::: seealso
 *     [method@Image.convsep].
 *
 * Returns: 0 on success, -1 on error
 */
int
vips_conv(VipsImage *in, VipsImage **out, VipsImage *mask, ...)
{
	va_list ap;
	int result;

	va_start(ap, mask);
	result = vips_call_split("conv", ap, in, out, mask);
	va_end(ap);

	return result;
}
