/* read and write masks
 */

/* Copyright: 1990, N. Dessipris.
 *
 * Author: Nicos Dessipris
 * Written on: 29/04/1991
 * Modified on: 10/8/1992, J.Cupitt
 *    -	Mask reading routines no longer fail if scale and offset are missing.
 *	Instead, they set default values of scale=1, offset=0.
 *    - Code tidied up, better error recovery.
 *    -	Bugs fixed in im_dup_*mask. No longer coredump.
 *    -	Bugs fixed in im_write_*mask. Now work for non-square matrices.
 *    -	im_copy_*mask_matrix, im_copy_matrix_*mask added: copy VIPS mask
 *	structures into Numerical Recipies in C style matrices and vice
 *	versa. Both structures should have been built before copy attempted.
 *	See im_create_*mask, im_*mat_alloc. The matrix should be indexed by 0
 *	to size-1.
 * 9/7/93 JC
 *    - some ANSIfication and tidies
 *    -	im_free_*mask() now return zero, so they can be used as close
 *	callbacks.
 * 7/10/94 JC
 *    - new IM_NEW(), IM_ARRAY() macros added
 * 27/4/95 JC
 *    -	oops! forgot to init IM_ARRAY() memory to zero
 * 7/8/96 JC
 *    - im_scale_dmask() rewritten
 * 7/5/98 JC
 *    - im_read_*mask() rewritten, now more robust
 *    - im_write_*mask() rewritten
 *    - new functions im_write_*mask_name()
 * 28/7/99 JC
 *    -	im_create_imaskv(), im_create_dmaskv() make masks and init from
 *	varargs
 *    - tabs allowed as column separators
 * 9/2/05
 *    - "," allowed as column separator ... helps CSV read
 * 31/5/06
 *    - use g_ascii_strtod() and friends
 * 2006-09-08 tcv
 *    - add im_norm_dmask()
 * 1/9/09
 * 	- move im_print_*mask() here
 * 12/11/09
 * 	- reading a float mask with im_read_imask() produced an incorrect
 * 	  error messagge
 * 21/10/10
 * 	- gtk-doc
 * 	- you can use commas to separate header fields
 * 	- small cleanups
 * 30/11/10
 * 	- im_scale_dmask() normalises to 20, not 100 ... we hit the fast
 * 	  conv path more often
 * 24/1/10
 * 	- oops, missing braces in write dmask removed spaces between items
 */

/*

	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 <stdarg.h>
#include <string.h>
#include <math.h>

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

/**
 * INTMASK:
 * @xsize: mask width
 * @ysize: mask height
 * @scale: mask scale factor
 * @offset: mask offset
 * @coeff: array of mask elements
 * @filename: the file this mask was read from, or should be written to
 *
 * An integer mask.
 *
 * @scale lets the mask represent fractional values: for
 * example, in integer convolution (see im_conv()) the result of the
 * convolution is divided by @scale and then added to @offset before being
 * written to the output image.
 *
 * @scale and @offset default to 1 and 0. Various functions, such as
 * im_conv(), will fail if @scale is zero.
 *
 * You can read and write the matrix elements in @coeff.
 */

/**
 * DOUBLEMASK:
 * @xsize: mask width
 * @ysize: mask height
 * @scale: mask scale factor
 * @offset: mask offset
 * @coeff: array of mask elements
 * @filename: the file this mask was read from, or should be written to
 *
 * A floating-point mask.
 *
 * As with #INTMASK, in convolution (see im_convf()) the result of the
 * convolution is divided by @scale and then added to @offset before being
 * written to the output image.
 *
 * @scale and @offset default to 1.0 and 0.0. Various functions, such as
 * im_conv(), will fail if @scale is zero.
 *
 * You can read and write the matrix elements in @coeff.
 */

/* Size of line buffer for reading.
 */
#define MAX_LINE (32768)

/**
 * im_free_imask:
 * @in: mask to free
 *
 * Free mask structure and any attached arrays. Return zero, so we can use
 * these functions as close callbacks.
 *
 * See also: im_free_dmask().
 *
 * Returns: zero.
 */
int
im_free_imask(INTMASK *in)
{
	if (!in)
		return 0;

	IM_FREE(in->coeff);
	IM_FREE(in->filename);
	IM_FREE(in);

	return 0;
}

/**
 * im_free_dmask:
 * @in: mask to free
 *
 * Free mask structure and any attached arrays. Return zero, so we can use
 * these functions as close callbacks.
 *
 * See also: im_free_dmask().
 *
 * Returns: zero.
 */
int
im_free_dmask(DOUBLEMASK *in)
{
	if (!in)
		return 0;

	IM_FREE(in->coeff);
	IM_FREE(in->filename);
	IM_FREE(in);

	return 0;
}

/**
 * im_create_imask:
 * @filename: set mask filename to this
 * @xsize: mask width
 * @ysize: mask height
 *
 * Create an empty imask. You need to loop over @coeff to set the values.
 *
 * See also: im_create_imaskv().
 *
 * Returns: The newly-allocated mask.
 */
INTMASK *
im_create_imask(const char *filename, int xsize, int ysize)
{
	INTMASK *out;
	int size = xsize * ysize;

	/* Check args.
	 */
	if (xsize <= 0 || ysize <= 0 || filename == NULL) {
		im_error("im_create_imask", "%s", _("bad arguments"));
		return NULL;
	}

	/* Allocate and initialise structure.
	 */
	if (!(out = IM_NEW(NULL, INTMASK)))
		return NULL;
	out->coeff = NULL;
	out->filename = NULL;
	out->scale = 1;
	out->offset = 0;
	out->xsize = 0;
	out->ysize = 0;

	if (!(out->coeff = IM_ARRAY(NULL, size, int))) {
		im_free_imask(out);
		return NULL;
	}
	(void) memset((char *) out->coeff, 0, size * sizeof(int));
	if (!(out->filename = im_strdup(NULL, filename))) {
		im_free_imask(out);
		return NULL;
	}
	out->xsize = xsize;
	out->ysize = ysize;

	return out;
}

/**
 * im_create_imaskv:
 * @filename: set mask filename to this
 * @xsize: mask width
 * @ysize: mask height
 * @...: values to set for the mask
 *
 * Create an imask and initialise it from the function parameter list.
 *
 * See also: im_create_imask().
 *
 * Returns: The newly-allocated mask.
 */
INTMASK *
im_create_imaskv(const char *filename, int xsize, int ysize, ...)
{
	va_list ap;

	INTMASK *out;
	int i;

	if (!(out = im_create_imask(filename, xsize, ysize)))
		return NULL;

	va_start(ap, ysize);
	for (i = 0; i < xsize * ysize; i++)
		out->coeff[i] = va_arg(ap, int);
	va_end(ap);

	return out;
}

/**
 * im_create_dmask:
 * @filename: set mask filename to this
 * @xsize: mask width
 * @ysize: mask height
 *
 * Create an empty dmask. You need to loop over @coeff to set the values.
 *
 * See also: im_create_dmaskv(), im_vips2mask().
 *
 * Returns: The newly-allocated mask.
 */
DOUBLEMASK *
im_create_dmask(const char *filename, int xsize, int ysize)
{
	DOUBLEMASK *out;
	int size = xsize * ysize;

	/* Check args.
	 */
	if (xsize <= 0 || ysize <= 0 || filename == NULL) {
		im_error("im_create_dmask", "%s", _("bad arguments"));
		return NULL;
	}

	/* Allocate and initialise structure.
	 */
	if (!(out = IM_NEW(NULL, DOUBLEMASK)))
		return NULL;
	out->coeff = NULL;
	out->filename = NULL;
	out->scale = 1.0;
	out->offset = 0.0;
	out->xsize = 0;
	out->ysize = 0;

	if (!(out->coeff = IM_ARRAY(NULL, size, double))) {
		im_free_dmask(out);
		return NULL;
	}
	(void) memset((char *) out->coeff, 0, size * sizeof(double));
	if (!(out->filename = im_strdup(NULL, filename))) {
		im_free_dmask(out);
		return NULL;
	}
	out->xsize = xsize;
	out->ysize = ysize;

	return out;
}

/**
 * im_create_dmaskv:
 * @filename: set mask filename to this
 * @xsize: mask width
 * @ysize: mask height
 * @...: values to set for the mask
 *
 * Create a dmask and initialise it from the function parameter list.
 *
 * See also: im_create_dmask().
 *
 * Returns: The newly-allocated mask.
 */
DOUBLEMASK *
im_create_dmaskv(const char *filename, int xsize, int ysize, ...)
{
	va_list ap;

	DOUBLEMASK *out;
	int i;

	if (!(out = im_create_dmask(filename, xsize, ysize)))
		return NULL;

	va_start(ap, ysize);
	for (i = 0; i < xsize * ysize; i++)
		out->coeff[i] = va_arg(ap, double);
	va_end(ap);

	return out;
}

/* Read a line from a file!
 */
static int
get_line(FILE *fp, char *buf)
{
	if (!fgets(buf, MAX_LINE, fp)) {
		im_error("read_mask", "%s", _("unexpected EOF"));
		return -1;
	}

	return 0;
}

/* width, height, optional scale, optional offset.
 */
static int
read_header(FILE *fp, int *xs, int *ys, double *scale, double *offset)
{
	char buf[MAX_LINE];
	char *p, *q;
	double v[4];
	int i;

	/* Read the first line: should contain size and optional
	 * scale + offset.
	 */
	if (get_line(fp, buf))
		return -1;

	/* Read as space separated doubles. \n is in the break list because
	 * our line will (usually) have a trailing \n which we want to count
	 * as whitespace.
	 */
	p = buf;
	for (i = 0, p = buf;
		 i < 4 && (q = im_break_token(p, " \";,\t\n"));
		 i++, p = q)
		v[i] = g_ascii_strtod(p, NULL);

	if ((i != 2 && i != 4) ||
		ceil(v[0]) != v[0] ||
		ceil(v[1]) != v[1] ||
		v[0] <= 0 ||
		v[1] <= 0) {
		im_error("read_header",
			"%s", _("error reading matrix header"));
		return -1;
	}
	if (i == 4 && v[2] == 0) {
		im_error("read_header",
			"%s", _("scale should be non-zero"));
		return -1;
	}

	*xs = v[0];
	*ys = v[1];
	if (i == 2) {
		*scale = 1.0;
		*offset = 0.0;
	}
	else {
		*scale = v[2];
		*offset = v[3];
	}

	return 0;
}

/**
 * im_read_dmask:
 * @filename: read matrix from this file
 *
 * Reads a matrix from a file.
 *
 * Matrix files have a simple format that's supposed to be easy to create with
 * a text editor or a spreadsheet.
 *
 * The first line has four numbers for width, height, scale and
 * offset (scale and offset may be omitted, in which case they default to 1.0
 * and 0.0). Scale must be non-zero. Width and height must be positive
 * integers. The numbers are separated by any mixture of spaces, commas,
 * tabs and quotation marks ("). The scale and offset fields may be
 * floating-point, and must use '.'
 * as a decimal separator.
 *
 * Subsequent lines each hold one line of matrix data, with numbers again
 * separated by any mixture of spaces, commas,
 * tabs and quotation marks ("). The numbers may be floating-point, and must
 * use '.'
 * as a decimal separator.
 *
 * Extra characters at the ends of lines or at the end of the file are
 * ignored.
 *
 * See also: im_read_imask(), im_gauss_dmask().
 *
 * Returns: the loaded mask on success, or NULL on error.
 */
DOUBLEMASK *
im_read_dmask(const char *filename)
{
	FILE *fp;
	double sc, off;
	int xs, ys;
	DOUBLEMASK *out;
	int x, y, i;
	char buf[MAX_LINE];

	if (!(fp = im__file_open_read(filename, NULL, TRUE)))
		return NULL;

	if (read_header(fp, &xs, &ys, &sc, &off)) {
		fclose(fp);
		return NULL;
	}

	if (!(out = im_create_dmask(filename, xs, ys))) {
		fclose(fp);
		return NULL;
	}
	out->scale = sc;
	out->offset = off;

	for (i = 0, y = 0; y < ys; y++) {
		char *p;

		if (get_line(fp, buf)) {
			im_free_dmask(out);
			fclose(fp);
			return NULL;
		}

		for (p = buf, x = 0; p && x < xs;
			 x++, i++, p = im_break_token(p, " \t,\";"))
			out->coeff[i] = g_ascii_strtod(p, NULL);
	}
	fclose(fp);

	return out;
}

/**
 * im_read_imask:
 * @filename: read matrix from this file
 *
 * Reads an integer matrix from a file.
 *
 * This function works exactly as im_read_dmask(), but the loaded matrix is
 * checked for 'int-ness'. All coefficients must be integers, and scale and
 * offset must be integers.
 *
 * See also: im_read_dmask().
 *
 * Returns: the loaded mask on success, or NULL on error.
 */
INTMASK *
im_read_imask(const char *filename)
{
	DOUBLEMASK *dmask;
	INTMASK *imask;
	int i;

	if (!(dmask = im_read_dmask(filename)))
		return NULL;

	if (ceil(dmask->scale) != dmask->scale ||
		ceil(dmask->offset) != dmask->offset) {
		im_error("im_read_imask",
			"%s", _("scale and offset should be int"));
		im_free_dmask(dmask);

		return NULL;
	}

	for (i = 0; i < dmask->xsize * dmask->ysize; i++)
		if (ceil(dmask->coeff[i]) != dmask->coeff[i]) {
			im_error("im_read_imask", _("ceofficient at "
										"position (%d, %d) is not int"),
				i % dmask->xsize,
				i / dmask->xsize);
			im_free_dmask(dmask);

			return NULL;
		}

	if (!(imask = im_create_imask(filename,
			  dmask->xsize, dmask->ysize))) {
		im_free_dmask(dmask);
		return NULL;
	}
	imask->scale = dmask->scale;
	imask->offset = dmask->offset;
	for (i = 0; i < dmask->xsize * dmask->ysize; i++)
		imask->coeff[i] = dmask->coeff[i];

	im_free_dmask(dmask);

	return imask;
}

/**
 * im_scale_dmask:
 * @in: mask to scale
 * @filename: filename for returned mask
 *
 * Scale the dmask to make an imask with a maximum value of 20.
 *
 * See also: im_norm_dmask().
 *
 * Returns: the converted mask, or NULL on error.
 */
INTMASK *
im_scale_dmask(DOUBLEMASK *in, const char *filename)
{
	const int size = in->xsize * in->ysize;

	INTMASK *out;
	double maxval, dsum;
	int i;
	int isum;

	if (im_check_dmask("im_scale_dmask", in) ||
		!(out = im_create_imask(filename, in->xsize, in->ysize)))
		return NULL;

	/* Find mask max.
	 */
	maxval = in->coeff[0];
	for (i = 0; i < size; i++)
		if (in->coeff[i] > maxval)
			maxval = in->coeff[i];

	/* Copy and scale, setting max to 20.
	 */
	for (i = 0; i < size; i++)
		out->coeff[i] = IM_RINT(in->coeff[i] * 20.0 / maxval);
	out->offset = in->offset;

	/* Set the scale to match the adjustment to max.
	 */
	isum = 0;
	dsum = 0.0;
	for (i = 0; i < size; i++) {
		isum += out->coeff[i];
		dsum += in->coeff[i];
	}

	if (dsum == in->scale)
		out->scale = isum;
	else if (dsum == 0.0)
		out->scale = 1.0;
	else
		out->scale = IM_RINT(in->scale * isum / dsum);

	return out;
}

/**
 * im_dmask2imask:
 * @in: mask to convert
 * @filename: filename for returned mask
 *
 * Make an imask from the dmask, rounding to nearest.
 *
 * See also: im_scale_dmask().
 *
 * Returns: the converted mask, or NULL on error.
 */
INTMASK *
im_dmask2imask(DOUBLEMASK *in, const char *filename)
{
	const int size = in->xsize * in->ysize;

	INTMASK *out;
	int i;

	if (im_check_dmask("im_dmask2imask", in) ||
		!(out = im_create_imask(filename, in->xsize, in->ysize)))
		return NULL;

	for (i = 0; i < size; i++)
		out->coeff[i] = IM_RINT(in->coeff[i]);
	out->offset = IM_RINT(in->offset);
	out->scale = IM_RINT(in->scale);

	return out;
}

/**
 * im_imask2dmask:
 * @in: mask to convert
 * @filename: filename for returned mask
 *
 * Make a dmask from the imask.
 *
 * See also: im_dmask2imask().
 *
 * Returns: the converted mask, or NULL on error.
 */
DOUBLEMASK *
im_imask2dmask(INTMASK *in, const char *filename)
{
	const int size = in->xsize * in->ysize;

	DOUBLEMASK *out;
	int i;

	if (im_check_imask("im_imask2dmask", in) ||
		!(out = im_create_dmask(filename, in->xsize, in->ysize)))
		return NULL;

	for (i = 0; i < size; i++)
		out->coeff[i] = in->coeff[i];
	out->offset = in->offset;
	out->scale = in->scale;

	return out;
}

/**
 * im_norm_dmask:
 * @mask: mask to scale
 *
 * Normalise the dmask. Apply the scale and offset to each element to make
 * a mask with scale 1, offset zero.
 *
 * See also: im_scale_dmask().
 *
 * Returns: 0 on success, or -1 on error.
 */
void
im_norm_dmask(DOUBLEMASK *mask)
{
	const int n = mask->xsize * mask->ysize;
	const double scale = (mask->scale == 0) ? 0 : (1.0 / mask->scale);

	int i;

	if (im_check_dmask("im_norm_dmask", mask) ||
		(1.0 == scale && 0.0 == mask->offset))
		return;

	for (i = 0; i < n; i++)
		mask->coeff[i] = mask->coeff[i] * scale + mask->offset;

	mask->scale = 1.0;
	mask->offset = 0.0;
}

/**
 * im_dup_imask:
 * @in: mask to duplicate
 * @filename: filename to set for the new mask
 *
 * Duplicate an imask.
 *
 * See also: im_dup_dmask().
 *
 * Returns: the mask copy, or NULL on error.
 */
INTMASK *
im_dup_imask(INTMASK *in, const char *filename)
{
	INTMASK *out;
	int i;

	if (im_check_imask("im_dup_imask", in) ||
		!(out = im_create_imask(filename, in->xsize, in->ysize)))
		return NULL;

	out->offset = in->offset;
	out->scale = in->scale;

	for (i = 0; i < in->xsize * in->ysize; i++)
		out->coeff[i] = in->coeff[i];

	return out;
}

/**
 * im_dup_dmask:
 * @in: mask to duplicate
 * @filename: filename to set for the new mask
 *
 * Duplicate a dmask.
 *
 * See also: im_dup_imask().
 *
 * Returns: the mask copy, or NULL on error.
 */
DOUBLEMASK *
im_dup_dmask(DOUBLEMASK *in, const char *filename)
{
	DOUBLEMASK *out;
	int i;

	if (im_check_dmask("im_dup_dmask", in) ||
		!(out = im_create_dmask(filename, in->xsize, in->ysize)))
		return NULL;

	out->offset = in->offset;
	out->scale = in->scale;

	for (i = 0; i < in->xsize * in->ysize; i++)
		out->coeff[i] = in->coeff[i];

	return out;
}

/* Write to file.
 */
static int
write_line(FILE *fp, const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	if (!vfprintf(fp, fmt, ap)) {
		im_error("write_mask", "%s", _("write error, disc full?"));
		return -1;
	}
	va_end(ap);

	return 0;
}

static int
write_double(FILE *fp, double d)
{
	char buf[G_ASCII_DTOSTR_BUF_SIZE];

	fprintf(fp, "%s", g_ascii_dtostr(buf, sizeof(buf), d));

	return 0;
}

/**
 * im_write_imask_name:
 * @in: mask to write
 * @filename: filename to write to
 *
 * Write an imask to a file. See im_read_dmask() for a description of the mask
 * file format.
 *
 * See also: im_write_imask().
 *
 * Returns: 0 on success, or -1 on error.
 */
int
im_write_imask_name(INTMASK *in, const char *filename)
{
	FILE *fp;
	int x, y, i;

	if (im_check_imask("im_write_imask_name", in) ||
		!(fp = im__file_open_write(filename, TRUE)))
		return -1;

	if (write_line(fp, "%d %d", in->xsize, in->ysize)) {
		fclose(fp);
		return -1;
	}
	if (in->scale != 1 || in->offset != 0)
		write_line(fp, " %d %d", in->scale, in->offset);
	write_line(fp, "\n");

	for (i = 0, y = 0; y < in->ysize; y++) {
		for (x = 0; x < in->xsize; x++, i++)
			write_line(fp, "%d ", in->coeff[i]);

		if (write_line(fp, "\n")) {
			fclose(fp);
			return -1;
		}
	}
	fclose(fp);

	return 0;
}

/**
 * im_write_imask:
 * @in: mask to write
 *
 * Write an imask to a file.
 *
 * See also: im_write_imask_name().
 *
 * Returns: 0 on success, or -1 on error.
 */
int
im_write_imask(INTMASK *in)
{
	if (!in->filename) {
		im_error("im_write_imask", "%s", _("filename not set"));
		return -1;
	}

	return im_write_imask_name(in, in->filename);
}

/**
 * im_write_dmask_name:
 * @in: mask to write
 * @filename: filename to write to
 *
 * Write a dmask to a file. See im_read_dmask() for a description of the mask
 * file format.
 *
 * See also: im_write_dmask().
 *
 * Returns: 0 on success, or -1 on error.
 */
int
im_write_dmask_name(DOUBLEMASK *in, const char *filename)
{
	FILE *fp;
	int x, y, i;

	if (im_check_dmask("im_write_dmask_name", in) ||
		!(fp = im__file_open_write(filename, TRUE)))
		return -1;

	if (write_line(fp, "%d %d", in->xsize, in->ysize)) {
		fclose(fp);
		return -1;
	}
	if (in->scale != 1.0 || in->offset != 0.0) {
		write_line(fp, " ");
		write_double(fp, in->scale);
		write_line(fp, " ");
		write_double(fp, in->offset);
	}
	write_line(fp, "\n");

	for (i = 0, y = 0; y < in->ysize; y++) {
		for (x = 0; x < in->xsize; x++, i++) {
			write_double(fp, in->coeff[i]);
			write_line(fp, " ");
		}

		if (write_line(fp, "\n")) {
			fclose(fp);
			return -1;
		}
	}
	fclose(fp);

	return 0;
}

/**
 * im_write_dmask:
 * @in: mask to write
 *
 * Write a dmask to a file. See im_read_dmask() for a description of the mask
 * file format.
 *
 * See also: im_write_dmask_name().
 *
 * Returns: 0 on success, or -1 on error.
 */
int
im_write_dmask(DOUBLEMASK *in)
{
	if (!in->filename) {
		im_error("im_write_dmask", "%s", _("filename not set"));
		return -1;
	}

	return im_write_dmask_name(in, in->filename);
}

/* Copy an imask into a matrix. Only used internally by matrix package for
 * invert.
 */
void
im_copy_imask_matrix(INTMASK *mask, int **matrix)
{
	int x, y;
	int *p = mask->coeff;

	for (y = 0; y < mask->ysize; y++)
		for (x = 0; x < mask->xsize; x++)
			matrix[x][y] = *p++;
}

/* Copy a matrix into an imask.
 */
void
im_copy_matrix_imask(int **matrix, INTMASK *mask)
{
	int x, y;
	int *p = mask->coeff;

	for (y = 0; y < mask->ysize; y++)
		for (x = 0; x < mask->xsize; x++)
			*p++ = matrix[x][y];
}

/* Copy a dmask into a matrix.
 */
void
im_copy_dmask_matrix(DOUBLEMASK *mask, double **matrix)
{
	int x, y;
	double *p = mask->coeff;

	for (y = 0; y < mask->ysize; y++)
		for (x = 0; x < mask->xsize; x++)
			matrix[x][y] = *p++;
}

/* Copy a matrix to a dmask.
 */
void
im_copy_matrix_dmask(double **matrix, DOUBLEMASK *mask)
{
	int x, y;
	double *p = mask->coeff;

	for (y = 0; y < mask->ysize; y++)
		for (x = 0; x < mask->xsize; x++)
			*p++ = matrix[x][y];
}

/**
 * im_print_imask:
 * @in: mask to print
 *
 * Print an imask to stdout.
 *
 * See also: im_print_dmask().
 */
void
im_print_imask(INTMASK *in)
{
	int i, j, k;

	printf("%s: %d %d %d %d\n",
		in->filename, in->xsize, in->ysize, in->scale, in->offset);

	for (k = 0, j = 0; j < in->ysize; j++) {
		for (i = 0; i < in->xsize; i++, k++)
			printf("%d\t", in->coeff[k]);

		printf("\n");
	}
}

/**
 * im_print_dmask:
 * @in: mask to print
 *
 * Print a dmask to stdout.
 *
 * See also: im_print_imask().
 */
void
im_print_dmask(DOUBLEMASK *in)
{
	int i, j, k;

	printf("%s: %d %d %f %f\n",
		in->filename, in->xsize, in->ysize, in->scale, in->offset);

	for (k = 0, j = 0; j < in->ysize; j++) {
		for (i = 0; i < in->xsize; i++, k++)
			printf("%f\t", in->coeff[k]);

		printf("\n");
	}
}

/**
 * im_local_dmask:
 * @out: image to make the mask local to
 * @mask: mask to local-ize
 *
 * @out takes ownership of @mask: when @out is closed, @mask will be closed
 * for you. If im_local_dmask() itself fails, the mask is also freed.
 *
 * See also: im_local_imask().
 *
 * Returns: the mask, or NULL on error.
 */
DOUBLEMASK *
im_local_dmask(VipsImage *out, DOUBLEMASK *mask)
{
	if (im_check_dmask("im_local_dmask", mask))
		return NULL;

	if (im_add_close_callback(out,
			(im_callback_fn) im_free_dmask, mask, NULL)) {
		im_free_dmask(mask);
		return NULL;
	}

	return mask;
}

/**
 * im_local_imask:
 * @out: image to make the mask local to
 * @mask: mask to local-ize
 *
 * @out takes ownership of @mask: when @out is closed, @mask will be closed
 * for you. If im_local_imask() itself fails, the mask is also freed.
 *
 * See also: im_local_dmask().
 *
 * Returns: the mask, or NULL on error.
 */
INTMASK *
im_local_imask(VipsImage *out, INTMASK *mask)
{
	if (im_check_imask("im_local_dmask", mask))
		return NULL;

	if (im_add_close_callback(out,
			(im_callback_fn) im_free_imask, mask, NULL)) {
		im_free_imask(mask);
		return NULL;
	}

	return mask;
}
