/* Object part of VImage class
 *
 * 30/12/14
 * 	- allow set enum value from string
 * 10/6/16
 * 	- missing implementation of VImage::write()
 * 11/6/16
 * 	- added arithmetic assignment overloads, += etc.
 */

/*

	Copyright (C) 1991-2001 The National Gallery

	This program 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 <vips/vips8>

#include <vips/debug.h>

/*
#define VIPS_DEBUG
#define VIPS_DEBUG_VERBOSE
 */

VIPS_NAMESPACE_START

/**
 * \namespace vips
 *
 * General docs for the vips namespace.
 */

std::vector<double>
to_vectorv(int n, ...)
{
	std::vector<double> vector(n);
	va_list ap;

	va_start(ap, n);
	for (int i = 0; i < n; i++)
		vector[i] = va_arg(ap, double);
	va_end(ap);

	return vector;
}

std::vector<double>
to_vector(double value)
{
	return to_vectorv(1, value);
}

std::vector<double>
to_vector(int n, double array[])
{
	std::vector<double> vector(n);

	for (int i = 0; i < n; i++)
		vector[i] = array[i];

	return vector;
}

std::vector<double>
negate(std::vector<double> vector)
{
	std::vector<double> new_vector(vector.size());

	for (std::vector<double>::size_type i = 0; i < vector.size(); i++)
		new_vector[i] = vector[i] * -1;

	return new_vector;
}

std::vector<double>
invert(std::vector<double> vector)
{
	std::vector<double> new_vector(vector.size());

	for (std::vector<double>::size_type i = 0; i < vector.size(); i++)
		new_vector[i] = 1.0 / vector[i];

	return new_vector;
}

VOption::~VOption()
{
	std::list<Pair *>::iterator i;

	for (i = options.begin(); i != options.end(); ++i)
		delete *i;
}

// input bool
VOption *
VOption::set(const char *name, bool value)
{
	Pair *pair = new Pair(name);

	pair->input = true;
	g_value_init(&pair->value, G_TYPE_BOOLEAN);
	g_value_set_boolean(&pair->value, value);
	options.push_back(pair);

	return this;
}

// input int ... this path is used for enums as well
VOption *
VOption::set(const char *name, int value)
{
	Pair *pair = new Pair(name);

	pair->input = true;
	g_value_init(&pair->value, G_TYPE_INT);
	g_value_set_int(&pair->value, value);
	options.push_back(pair);

	return this;
}

// input guint64
VOption *
VOption::set(const char *name, guint64 value)
{
	Pair *pair = new Pair(name);

	pair->input = true;
	g_value_init(&pair->value, G_TYPE_UINT64);
	g_value_set_uint64(&pair->value, value);
	options.push_back(pair);

	return this;
}

// input double
VOption *
VOption::set(const char *name, double value)
{
	Pair *pair = new Pair(name);

	pair->input = true;
	g_value_init(&pair->value, G_TYPE_DOUBLE);
	g_value_set_double(&pair->value, value);
	options.push_back(pair);

	return this;
}

VOption *
VOption::set(const char *name, const char *value)
{
	Pair *pair = new Pair(name);

	pair->input = true;
	g_value_init(&pair->value, G_TYPE_STRING);
	g_value_set_string(&pair->value, value);
	options.push_back(pair);

	return this;
}

// input vips object (image, source, target, etc. etc.)
VOption *
VOption::set(const char *name, const VObject value)
{
	Pair *pair = new Pair(name);
	VipsObject *object = value.get_object();
	GType type = G_OBJECT_TYPE(object);

	pair->input = true;
	g_value_init(&pair->value, type);
	g_value_set_object(&pair->value, object);
	options.push_back(pair);

	return this;
}

// input int array
VOption *
VOption::set(const char *name, std::vector<int> value)
{
	Pair *pair = new Pair(name);

	int *array;

	pair->input = true;

	g_value_init(&pair->value, VIPS_TYPE_ARRAY_INT);
	vips_value_set_array_int(&pair->value, nullptr,
		static_cast<int>(value.size()));
	array = vips_value_get_array_int(&pair->value, nullptr);

	for (std::vector<double>::size_type i = 0; i < value.size(); i++)
		array[i] = value[i];

	options.push_back(pair);

	return this;
}

// input double array
VOption *
VOption::set(const char *name, std::vector<double> value)
{
	Pair *pair = new Pair(name);

	double *array;

	pair->input = true;

	g_value_init(&pair->value, VIPS_TYPE_ARRAY_DOUBLE);
	vips_value_set_array_double(&pair->value, nullptr,
		static_cast<int>(value.size()));
	array = vips_value_get_array_double(&pair->value, nullptr);

	for (std::vector<double>::size_type i = 0; i < value.size(); i++)
		array[i] = value[i];

	options.push_back(pair);

	return this;
}

// input image array
VOption *
VOption::set(const char *name, std::vector<VImage> value)
{
	Pair *pair = new Pair(name);

	VipsImage **array;

	pair->input = true;

	g_value_init(&pair->value, VIPS_TYPE_ARRAY_IMAGE);
	vips_value_set_array_image(&pair->value,
		static_cast<int>(value.size()));
	array = vips_value_get_array_image(&pair->value, nullptr);

	for (std::vector<double>::size_type i = 0; i < value.size(); i++) {
		VipsImage *vips_image = value[i].get_image();

		array[i] = vips_image;
		g_object_ref(vips_image);
	}

	options.push_back(pair);

	return this;
}

// input blob
VOption *
VOption::set(const char *name, VipsBlob *value)
{
	Pair *pair = new Pair(name);

	pair->input = true;
	g_value_init(&pair->value, VIPS_TYPE_BLOB);
	g_value_set_boxed(&pair->value, value);
	options.push_back(pair);

	return this;
}

// output bool
VOption *
VOption::set(const char *name, bool *value)
{
	Pair *pair = new Pair(name);

	pair->input = false;
	pair->vbool = value;
	g_value_init(&pair->value, G_TYPE_BOOLEAN);

	options.push_back(pair);

	return this;
}

// output int
VOption *
VOption::set(const char *name, int *value)
{
	Pair *pair = new Pair(name);

	pair->input = false;
	pair->vint = value;
	g_value_init(&pair->value, G_TYPE_INT);

	options.push_back(pair);

	return this;
}

// output double
VOption *
VOption::set(const char *name, double *value)
{
	Pair *pair = new Pair(name);

	pair->input = false;
	pair->vdouble = value;
	g_value_init(&pair->value, G_TYPE_DOUBLE);

	options.push_back(pair);

	return this;
}

// output image
VOption *
VOption::set(const char *name, VImage *value)
{
	Pair *pair = new Pair(name);

	pair->input = false;
	pair->vimage = value;
	g_value_init(&pair->value, VIPS_TYPE_IMAGE);

	options.push_back(pair);

	return this;
}

// output doublearray
VOption *
VOption::set(const char *name, std::vector<double> *value)
{
	Pair *pair = new Pair(name);

	pair->input = false;
	pair->vvector = value;
	g_value_init(&pair->value, VIPS_TYPE_ARRAY_DOUBLE);

	options.push_back(pair);

	return this;
}

// output blob
VOption *
VOption::set(const char *name, VipsBlob **value)
{
	Pair *pair = new Pair(name);

	pair->input = false;
	pair->vblob = value;
	g_value_init(&pair->value, VIPS_TYPE_BLOB);

	options.push_back(pair);

	return this;
}

// just g_object_set_property(), except we allow set enum from string
static void
set_property(VipsObject *object, const char *name, const GValue *value)
{
	VipsObjectClass *object_class = VIPS_OBJECT_GET_CLASS(object);
	GType type = G_VALUE_TYPE(value);

	GParamSpec *pspec;
	VipsArgumentClass *argument_class;
	VipsArgumentInstance *argument_instance;

	if (vips_object_get_argument(object, name,
			&pspec, &argument_class, &argument_instance)) {
		g_warning("%s", vips_error_buffer());
		vips_error_clear();
		return;
	}

	if (G_IS_PARAM_SPEC_ENUM(pspec) &&
		type == G_TYPE_STRING) {
		GType pspec_type = G_PARAM_SPEC_VALUE_TYPE(pspec);

		int enum_value;
		GValue value2 = G_VALUE_INIT;

		if ((enum_value = vips_enum_from_nick(object_class->nickname,
				 pspec_type, g_value_get_string(value))) < 0) {
			g_warning("%s", vips_error_buffer());
			vips_error_clear();
			return;
		}

		g_value_init(&value2, pspec_type);
		g_value_set_enum(&value2, enum_value);
		g_object_set_property(G_OBJECT(object), name, &value2);
		g_value_unset(&value2);
	}
	else
		g_object_set_property(G_OBJECT(object), name, value);
}

// walk the options and set props on the operation
void
VOption::set_operation(VipsOperation *operation)
{
	std::list<Pair *>::iterator i;

	for (i = options.begin(); i != options.end(); ++i)
		if ((*i)->input) {
#ifdef VIPS_DEBUG_VERBOSE
			printf("set_operation: ");
			vips_object_print_name(VIPS_OBJECT(operation));
			char *str_value = g_strdup_value_contents(&(*i)->value);
			printf(".%s = %s\n", (*i)->name, str_value);
			g_free(str_value);
#endif /*VIPS_DEBUG_VERBOSE*/

			set_property(VIPS_OBJECT(operation),
				(*i)->name, &(*i)->value);
		}
}

// walk the options and fetch any requested outputs
void
VOption::get_operation(VipsOperation *operation)
{
	std::list<Pair *>::iterator i;

	for (i = options.begin(); i != options.end(); ++i)
		if (!(*i)->input) {
			const char *name = (*i)->name;

			g_object_get_property(G_OBJECT(operation), name, &(*i)->value);

#ifdef VIPS_DEBUG_VERBOSE
			printf("get_operation: ");
			vips_object_print_name(VIPS_OBJECT(operation));
			char *str_value = g_strdup_value_contents(&(*i)->value);
			printf(".%s = %s\n", name, str_value);
			g_free(str_value);
#endif /*VIPS_DEBUG_VERBOSE*/

			GValue *value = &(*i)->value;
			GType type = G_VALUE_TYPE(value);

			if (type == VIPS_TYPE_IMAGE) {
				// rebox object
				VipsImage *image = VIPS_IMAGE(g_value_get_object(value));
				*((*i)->vimage) = VImage(image, NOSTEAL);
			}
			else if (type == G_TYPE_INT)
				*((*i)->vint) = g_value_get_int(value);
			else if (type == G_TYPE_BOOLEAN)
				*((*i)->vbool) = g_value_get_boolean(value);
			else if (type == G_TYPE_DOUBLE)
				*((*i)->vdouble) = g_value_get_double(value);
			else if (type == VIPS_TYPE_ARRAY_DOUBLE) {
				int length;
				double *array = vips_value_get_array_double(value, &length);

				((*i)->vvector)->resize(length);
				for (int j = 0; j < length; j++)
					(*((*i)->vvector))[j] = array[j];
			}
			else if (type == VIPS_TYPE_BLOB)
				// our caller gets a reference
				*((*i)->vblob) = (VipsBlob *) g_value_dup_boxed(value);
		}
}

void
VImage::call_option_string(const char *operation_name,
	const char *option_string, VOption *options)
{
	VipsOperation *operation;

	VIPS_DEBUG_MSG("call_option_string: starting for %s ...\n",
		operation_name);

	if (!(operation = vips_operation_new(operation_name))) {
		delete options;
		throw(VError());
	}

	/* Set str options before vargs options, so the user can't
	 * override things we set deliberately.
	 */
	if (option_string &&
		vips_object_set_from_string(VIPS_OBJECT(operation), option_string)) {
		vips_object_unref_outputs(VIPS_OBJECT(operation));
		g_object_unref(operation);
		delete options;
		throw(VError());
	}

	if (options)
		options->set_operation(operation);

	/* Build from cache.
	 */
	if (vips_cache_operation_buildp(&operation)) {
		vips_object_unref_outputs(VIPS_OBJECT(operation));
		g_object_unref(operation);
		delete options;
		throw(VError());
	}

	/* Walk args again, writing output.
	 */
	if (options)
		options->get_operation(operation);

	/* Unref all unassigned outputs.
	 */
	vips_object_unref_outputs(VIPS_OBJECT(operation));

	/* The operation we have built should now have been reffed by
	 * one of its arguments or have finished its work. Either
	 * way, we can unref.
	 */
	g_object_unref(operation);

	/* We're done with options!
	 */
	delete options;
}

void
VImage::call(const char *operation_name, VOption *options)
{
	call_option_string(operation_name, nullptr, options);
}

VImage
VImage::new_from_file(const char *name, VOption *options)
{
	char filename[VIPS_PATH_MAX];
	char option_string[VIPS_PATH_MAX];
	const char *operation_name;

	VImage out;

	vips__filename_split8(name, filename, option_string);
	if (!(operation_name = vips_foreign_find_load(filename))) {
		delete options;
		throw VError();
	}

	call_option_string(operation_name, option_string,
		(options ? options : VImage::option())
			->set("filename", filename)
			->set("out", &out));

	return out;
}

VImage
VImage::new_from_buffer(const void *buf, size_t len, const char *option_string,
	VOption *options)
{
	const char *operation_name;
	VipsBlob *blob;
	VImage out;

	if (!(operation_name = vips_foreign_find_load_buffer(buf, len))) {
		delete options;
		throw(VError());
	}

	/* We don't take a copy of the data or free it.
	 */
	blob = vips_blob_new(nullptr, buf, len);
	options = (options ? options : VImage::option())
				  ->set("buffer", blob)
				  ->set("out", &out);
	vips_area_unref(VIPS_AREA(blob));

	call_option_string(operation_name, option_string, options);

	return out;
}

VImage
VImage::new_from_buffer(const std::string &buf, const char *option_string,
	VOption *options)
{
	return new_from_buffer(buf.c_str(), buf.size(),
		option_string, options);
}

VImage
VImage::new_from_source(VSource source, const char *option_string,
	VOption *options)
{
	const char *operation_name;
	VImage out;

	if (!(operation_name = vips_foreign_find_load_source(
			  source.get_source()))) {
		delete options;
		throw(VError());
	}

	options = (options ? options : VImage::option())
				  ->set("source", source)
				  ->set("out", &out);

	call_option_string(operation_name, option_string, options);

	return out;
}

VImage
VImage::new_from_memory_steal(const void *data, size_t size,
	int width, int height, int bands, VipsBandFormat format)
{
	VipsImage *image;

	if (!(image = vips_image_new_from_memory(data, size,
			  width, height, bands, format)))
		throw(VError());

	g_signal_connect(image, "postclose",
		G_CALLBACK(vips_image_free_buffer), const_cast<void *>(data));

	return VImage(image);
}

VImage
VImage::new_matrix(int width, int height)
{
	return VImage(vips_image_new_matrix(width, height));
}

VImage
VImage::new_matrixv(int width, int height, ...)
{
	VImage matrix = new_matrix(width, height);
	VipsImage *vips_matrix = matrix.get_image();

	va_list ap;

	va_start(ap, height);
	for (int y = 0; y < height; y++)
		for (int x = 0; x < width; x++)
			*VIPS_MATRIX(vips_matrix, x, y) = va_arg(ap, double);
	va_end(ap);

	return matrix;
}

VImage
VImage::write(VImage out) const
{
	if (vips_image_write(this->get_image(), out.get_image()))
		throw VError();

	return out;
}

void
VImage::write_to_file(const char *name, VOption *options) const
{
	char filename[VIPS_PATH_MAX];
	char option_string[VIPS_PATH_MAX];
	const char *operation_name;

	vips__filename_split8(name, filename, option_string);
	if (!(operation_name = vips_foreign_find_save(filename))) {
		delete options;
		throw VError();
	}

	call_option_string(operation_name, option_string,
		(options ? options : VImage::option())
			->set("in", *this)
			->set("filename", filename));
}

void
VImage::write_to_buffer(const char *suffix, void **buf, size_t *size,
	VOption *options) const
{
	char filename[VIPS_PATH_MAX];
	char option_string[VIPS_PATH_MAX];
	const char *operation_name;
	VipsBlob *blob;

	/* Save with the new target API if we can. Fall back to the older
	 * mechanism in case the saver we need has not been converted yet.
	 *
	 * We need to hide any errors from this first phase.
	 */
	vips__filename_split8(suffix, filename, option_string);

	vips_error_freeze();
	operation_name = vips_foreign_find_save_target(filename);
	vips_error_thaw();

	if (operation_name) {
		VTarget target = VTarget::new_to_memory();

		call_option_string(operation_name, option_string,
			(options ? options : VImage::option())
				->set("in", *this)
				->set("target", target));

		g_object_get(target.get_target(), "blob", &blob, nullptr);
	}
	else if ((operation_name = vips_foreign_find_save_buffer(filename))) {
		call_option_string(operation_name, option_string,
			(options ? options : VImage::option())
				->set("in", *this)
				->set("buffer", &blob));
	}
	else {
		delete options;
		throw VError();
	}

	if (blob) {
		if (buf) {
			*buf = VIPS_AREA(blob)->data;
			VIPS_AREA(blob)->free_fn = nullptr;
		}
		if (size)
			*size = VIPS_AREA(blob)->length;

		vips_area_unref(VIPS_AREA(blob));
	}
}

void
VImage::write_to_target(const char *suffix, VTarget target,
	VOption *options) const
{
	char filename[VIPS_PATH_MAX];
	char option_string[VIPS_PATH_MAX];
	const char *operation_name;

	vips__filename_split8(suffix, filename, option_string);
	if (!(operation_name = vips_foreign_find_save_target(filename))) {
		delete options;
		throw VError();
	}

	call_option_string(operation_name, option_string,
		(options ? options : VImage::option())
			->set("in", *this)
			->set("target", target));
}

VImage
VImage::thumbnail_buffer(void *buf, size_t len, int width, VOption *options)
{
	VipsBlob *blob;
	VImage out;

	/* We don't take a copy of the data or free it.
	 */
	blob = vips_blob_new(nullptr, buf, len);
	options = (options ? options : VImage::option())
		->set("buffer", blob)
		->set("width", width)
		->set("out", &out);
	vips_area_unref(VIPS_AREA(blob));

	call("thumbnail_buffer", options);

	return out;
}

VRegion
VImage::region() const
{
	return VRegion::new_from_image(*this);
}

VRegion
VImage::region(VipsRect *rect) const
{
	VRegion region = VRegion::new_from_image(*this);

	region.prepare(rect);

	return region;
}

VRegion
VImage::region(int left, int top, int width, int height) const
{
	VRegion region = VRegion::new_from_image(*this);

	region.prepare(left, top, width, height);

	return region;
}

#include "vips-operators.cpp"

std::vector<VImage>
VImage::bandsplit(VOption *options) const
{
	std::vector<VImage> b;
	b.reserve(bands());

	for (int i = 0; i < bands(); i++)
		b.push_back(extract_band(i));

	return b;
}

VImage
VImage::bandjoin(VImage other, VOption *options) const
{
	VImage v[2] = { *this, other };
	std::vector<VImage> vec(v, v + VIPS_NUMBER(v));

	return bandjoin(vec, options);
}

VImage
VImage::composite(VImage other, VipsBlendMode mode, VOption *options) const
{
	VImage v[2] = { *this, other };
	std::vector<VImage> ivec(v, v + VIPS_NUMBER(v));
	int m[1] = { static_cast<int>(mode) };
	std::vector<int> mvec(m, m + VIPS_NUMBER(m));

	return composite(ivec, mvec, options);
}

std::complex<double>
VImage::minpos(VOption *options) const
{
	double x, y;

	(void) min((options ? options : VImage::option())
		->set("x", &x)
		->set("y", &y));

	return {x, y};
}

std::complex<double>
VImage::maxpos(VOption *options) const
{
	double x, y;

	(void) max((options ? options : VImage::option())
		->set("x", &x)
		->set("y", &y));

	return {x, y};
}

// Operator overloads

VImage
VImage::operator[](int index) const
{
	return this->extract_band(index);
}

std::vector<double>
VImage::operator()(int x, int y) const
{
	return this->getpoint(x, y);
}

VImage
operator+(const VImage a, const VImage b)
{
	return a.add(b);
}

VImage
operator+(double a, const VImage b)
{
	return b.linear(1.0, a);
}

VImage
operator+(const VImage a, double b)
{
	return a.linear(1.0, b);
}

VImage
operator+(const std::vector<double> a, const VImage b)
{
	return b.linear(1.0, a);
}

VImage
operator+(const VImage a, const std::vector<double> b)
{
	return a.linear(1.0, b);
}

VImage &
operator+=(VImage &a, const VImage b)
{
	a = a + b;
	return a;
}

VImage &
operator+=(VImage &a, const double b)
{
	a = a + b;
	return a;
}

VImage &
operator+=(VImage &a, const std::vector<double> b)
{
	a = a + b;
	return a;
}

VImage
operator-(const VImage a, const VImage b)
{
	return a.subtract(b);
}

VImage
operator-(double a, const VImage b)
{
	return b.linear(-1.0, a);
}

VImage
operator-(const VImage a, double b)
{
	return a.linear(1.0, -b);
}

VImage
operator-(const std::vector<double> a, const VImage b)
{
	return b.linear(-1.0, a);
}

VImage
operator-(const VImage a, const std::vector<double> b)
{
	return a.linear(1.0, vips::negate(b));
}

VImage &
operator-=(VImage &a, const VImage b)
{
	a = a - b;
	return a;
}

VImage &
operator-=(VImage &a, const double b)
{
	a = a - b;
	return a;
}

VImage &
operator-=(VImage &a, const std::vector<double> b)
{
	a = a - b;
	return a;
}

VImage
operator-(const VImage a)
{
	return a * -1;
}

VImage
operator*(const VImage a, const VImage b)
{
	return a.multiply(b);
}

VImage
operator*(double a, const VImage b)
{
	return b.linear(a, 0.0);
}

VImage
operator*(const VImage a, double b)
{
	return a.linear(b, 0.0);
}

VImage
operator*(const std::vector<double> a, const VImage b)
{
	return b.linear(a, 0.0);
}

VImage
operator*(const VImage a, const std::vector<double> b)
{
	return a.linear(b, 0.0);
}

VImage &
operator*=(VImage &a, const VImage b)
{
	a = a * b;
	return a;
}

VImage &
operator*=(VImage &a, const double b)
{
	a = a * b;
	return a;
}

VImage &
operator*=(VImage &a, const std::vector<double> b)
{
	a = a * b;
	return a;
}

VImage
operator/(const VImage a, const VImage b)
{
	return a.divide(b);
}

VImage
operator/(double a, const VImage b)
{
	return b.pow(-1.0).linear(a, 0.0);
}

VImage
operator/(const VImage a, double b)
{
	return a.linear(1.0 / b, 0.0);
}

VImage
operator/(const std::vector<double> a, const VImage b)
{
	return b.pow(-1.0).linear(a, 0.0);
}

VImage
operator/(const VImage a, const std::vector<double> b)
{
	return a.linear(vips::invert(b), 0.0);
}

VImage &
operator/=(VImage &a, const VImage b)
{
	a = a / b;
	return a;
}

VImage &
operator/=(VImage &a, const double b)
{
	a = a / b;
	return a;
}

VImage &
operator/=(VImage &a, const std::vector<double> b)
{
	a = a / b;
	return a;
}

VImage
operator%(const VImage a, const VImage b)
{
	return a.remainder(b);
}

VImage
operator%(const VImage a, const double b)
{
	return a.remainder_const(to_vector(b));
}

VImage
operator%(const VImage a, const std::vector<double> b)
{
	return a.remainder_const(b);
}

VImage &
operator%=(VImage &a, const VImage b)
{
	a = a % b;
	return a;
}

VImage &
operator%=(VImage &a, const double b)
{
	a = a % b;
	return a;
}

VImage &
operator%=(VImage &a, const std::vector<double> b)
{
	a = a % b;
	return a;
}

VImage
operator<(const VImage a, const VImage b)
{
	return a.relational(b, VIPS_OPERATION_RELATIONAL_LESS);
}

VImage
operator<(const double a, const VImage b)
{
	return b.relational_const(VIPS_OPERATION_RELATIONAL_MORE, to_vector(a));
}

VImage
operator<(const VImage a, const double b)
{
	return a.relational_const(VIPS_OPERATION_RELATIONAL_LESS, to_vector(b));
}

VImage
operator<(const std::vector<double> a, const VImage b)
{
	return b.relational_const(VIPS_OPERATION_RELATIONAL_MORE, a);
}

VImage
operator<(const VImage a, const std::vector<double> b)
{
	return a.relational_const(VIPS_OPERATION_RELATIONAL_LESS, b);
}

VImage
operator<=(const VImage a, const VImage b)
{
	return a.relational(b, VIPS_OPERATION_RELATIONAL_LESSEQ);
}

VImage
operator<=(const double a, const VImage b)
{
	return b.relational_const(VIPS_OPERATION_RELATIONAL_MOREEQ, to_vector(a));
}

VImage
operator<=(const VImage a, const double b)
{
	return a.relational_const(VIPS_OPERATION_RELATIONAL_LESSEQ, to_vector(b));
}

VImage
operator<=(const std::vector<double> a, const VImage b)
{
	return b.relational_const(VIPS_OPERATION_RELATIONAL_MOREEQ, a);
}

VImage
operator<=(const VImage a, const std::vector<double> b)
{
	return a.relational_const(VIPS_OPERATION_RELATIONAL_LESSEQ, b);
}

VImage
operator>(const VImage a, const VImage b)
{
	return a.relational(b, VIPS_OPERATION_RELATIONAL_MORE);
}

VImage
operator>(const double a, const VImage b)
{
	return b.relational_const(VIPS_OPERATION_RELATIONAL_LESS, to_vector(a));
}

VImage
operator>(const VImage a, const double b)
{
	return a.relational_const(VIPS_OPERATION_RELATIONAL_MORE, to_vector(b));
}

VImage
operator>(const std::vector<double> a, const VImage b)
{
	return b.relational_const(VIPS_OPERATION_RELATIONAL_LESS, a);
}

VImage
operator>(const VImage a, const std::vector<double> b)
{
	return a.relational_const(VIPS_OPERATION_RELATIONAL_MORE, b);
}

VImage
operator>=(const VImage a, const VImage b)
{
	return a.relational(b, VIPS_OPERATION_RELATIONAL_MOREEQ);
}

VImage
operator>=(const double a, const VImage b)
{
	return b.relational_const(VIPS_OPERATION_RELATIONAL_LESSEQ, to_vector(a));
}

VImage
operator>=(const VImage a, const double b)
{
	return a.relational_const(VIPS_OPERATION_RELATIONAL_MOREEQ, to_vector(b));
}

VImage
operator>=(const std::vector<double> a, const VImage b)
{
	return b.relational_const(VIPS_OPERATION_RELATIONAL_LESSEQ, a);
}

VImage
operator>=(const VImage a, const std::vector<double> b)
{
	return a.relational_const(VIPS_OPERATION_RELATIONAL_MOREEQ, b);
}

VImage
operator==(const VImage a, const VImage b)
{
	return a.relational(b, VIPS_OPERATION_RELATIONAL_EQUAL);
}

VImage
operator==(const double a, const VImage b)
{
	return b.relational_const(VIPS_OPERATION_RELATIONAL_EQUAL, to_vector(a));
}

VImage
operator==(const VImage a, const double b)
{
	return a.relational_const(VIPS_OPERATION_RELATIONAL_EQUAL, to_vector(b));
}

VImage
operator==(const std::vector<double> a, const VImage b)
{
	return b.relational_const(VIPS_OPERATION_RELATIONAL_EQUAL, a);
}

VImage
operator==(const VImage a, const std::vector<double> b)
{
	return a.relational_const(VIPS_OPERATION_RELATIONAL_EQUAL, b);
}

VImage
operator!=(const VImage a, const VImage b)
{
	return a.relational(b, VIPS_OPERATION_RELATIONAL_NOTEQ);
}

VImage
operator!=(const double a, const VImage b)
{
	return b.relational_const(VIPS_OPERATION_RELATIONAL_NOTEQ, to_vector(a));
}

VImage
operator!=(const VImage a, const double b)
{
	return a.relational_const(VIPS_OPERATION_RELATIONAL_NOTEQ, to_vector(b));
}

VImage
operator!=(const std::vector<double> a, const VImage b)
{
	return b.relational_const(VIPS_OPERATION_RELATIONAL_NOTEQ, a);
}

VImage
operator!=(const VImage a, const std::vector<double> b)
{
	return a.relational_const(VIPS_OPERATION_RELATIONAL_NOTEQ, b);
}

VImage
operator&(const VImage a, const VImage b)
{
	return a.boolean(b, VIPS_OPERATION_BOOLEAN_AND);
}

VImage
operator&(const double a, const VImage b)
{
	return b.boolean_const(VIPS_OPERATION_BOOLEAN_AND, to_vector(a));
}

VImage
operator&(const VImage a, const double b)
{
	return a.boolean_const(VIPS_OPERATION_BOOLEAN_AND, to_vector(b));
}

VImage
operator&(const std::vector<double> a, const VImage b)
{
	return b.boolean_const(VIPS_OPERATION_BOOLEAN_AND, a);
}

VImage
operator&(const VImage a, const std::vector<double> b)
{
	return a.boolean_const(VIPS_OPERATION_BOOLEAN_AND, b);
}

VImage &
operator&=(VImage &a, const VImage b)
{
	a = a & b;
	return a;
}

VImage &
operator&=(VImage &a, const double b)
{
	a = a & b;
	return a;
}

VImage &
operator&=(VImage &a, const std::vector<double> b)
{
	a = a & b;
	return a;
}

VImage
operator|(const VImage a, const VImage b)
{
	return a.boolean(b, VIPS_OPERATION_BOOLEAN_OR);
}

VImage
operator|(const double a, const VImage b)
{
	return b.boolean_const(VIPS_OPERATION_BOOLEAN_OR, to_vector(a));
}

VImage
operator|(const VImage a, const double b)
{
	return a.boolean_const(VIPS_OPERATION_BOOLEAN_OR, to_vector(b));
}

VImage
operator|(const std::vector<double> a, const VImage b)
{
	return b.boolean_const(VIPS_OPERATION_BOOLEAN_OR, a);
}

VImage
operator|(const VImage a, const std::vector<double> b)
{
	return a.boolean_const(VIPS_OPERATION_BOOLEAN_OR, b);
}

VImage &
operator|=(VImage &a, const VImage b)
{
	a = a | b;
	return a;
}

VImage &
operator|=(VImage &a, const double b)
{
	a = a | b;
	return a;
}

VImage &
operator|=(VImage &a, const std::vector<double> b)
{
	a = a | b;
	return a;
}

VImage
operator^(const VImage a, const VImage b)
{
	return a.boolean(b, VIPS_OPERATION_BOOLEAN_EOR);
}

VImage
operator^(const double a, const VImage b)
{
	return b.boolean_const(VIPS_OPERATION_BOOLEAN_EOR, to_vector(a));
}

VImage
operator^(const VImage a, const double b)
{
	return a.boolean_const(VIPS_OPERATION_BOOLEAN_EOR, to_vector(b));
}

VImage
operator^(const std::vector<double> a, const VImage b)
{
	return b.boolean_const(VIPS_OPERATION_BOOLEAN_EOR, a);
}

VImage
operator^(const VImage a, const std::vector<double> b)
{
	return a.boolean_const(VIPS_OPERATION_BOOLEAN_EOR, b);
}

VImage &
operator^=(VImage &a, const VImage b)
{
	a = a ^ b;
	return a;
}

VImage &
operator^=(VImage &a, const double b)
{
	a = a ^ b;
	return a;
}

VImage &
operator^=(VImage &a, const std::vector<double> b)
{
	a = a ^ b;
	return a;
}

VImage
operator<<(const VImage a, const VImage b)
{
	return a.boolean(b, VIPS_OPERATION_BOOLEAN_LSHIFT);
}

VImage
operator<<(const VImage a, const double b)
{
	return a.boolean_const(VIPS_OPERATION_BOOLEAN_LSHIFT, to_vector(b));
}

VImage
operator<<(const VImage a, const std::vector<double> b)
{
	return a.boolean_const(VIPS_OPERATION_BOOLEAN_LSHIFT, b);
}

VImage &
operator<<=(VImage &a, const VImage b)
{
	a = a << b;
	return a;
}

VImage &
operator<<=(VImage &a, const double b)
{
	a = a << b;
	return a;
}

VImage &
operator<<=(VImage &a, const std::vector<double> b)
{
	a = a << b;
	return a;
}

VImage
operator>>(const VImage a, const VImage b)
{
	return a.boolean(b, VIPS_OPERATION_BOOLEAN_RSHIFT);
}

VImage
operator>>(const VImage a, const double b)
{
	return a.boolean_const(VIPS_OPERATION_BOOLEAN_RSHIFT, to_vector(b));
}

VImage
operator>>(const VImage a, const std::vector<double> b)
{
	return a.boolean_const(VIPS_OPERATION_BOOLEAN_RSHIFT, b);
}

VImage &
operator>>=(VImage &a, const VImage b)
{
	a = a << b;
	return a;
}

VImage &
operator>>=(VImage &a, const double b)
{
	a = a << b;
	return a;
}

VImage &
operator>>=(VImage &a, const std::vector<double> b)
{
	a = a << b;
	return a;
}

// Compat operations

VImage
VImage::new_from_memory_steal(void *data, size_t size,
	int width, int height, int bands, VipsBandFormat format)
{
	return new_from_memory_steal(static_cast<const void *>(data), size,
		width, height, bands, format);
}

void
VImage::rawsave_fd(int fd, VOption *options) const
{
	rawsave_target(VTarget::new_to_descriptor(fd), options);
}

VIPS_NAMESPACE_END
