/* VipsEmbed
 *
 * Author: J. Cupitt
 * Written on: 21/2/95
 * Modified on: 
 * 6/4/04
 *	- added extend pixels from edge mode
 *	- sets Xoffset / Yoffset to x / y
 * 15/4/04 
 *	- added replicate and mirror modes
 * 4/3/05
 *	- added solid white mode
 * 4/1/07
 * 	- degenerate to im_copy() for 0/0/w/h
 * 1/8/07
 * 	- more general ... x and y can be negative
 * 24/3/09
 * 	- added IM_CODING_RAD support
 * 5/11/09
 * 	- gtkdoc
 * 27/1/10
 * 	- use im_region_paint()
 * 	- cleanups
 * 15/10/11
 * 	- rewrite as a class
 * 10/10/12
 *	- add @background
 * 19/9/17
 * 	- break into embed and gravity
 */

/*

    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

 */

/*
#define VIPS_DEBUG
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <vips/intl.h>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>

#include <vips/vips.h>
#include <vips/internal.h>
#include <vips/debug.h>

#include "pconversion.h"

typedef struct _VipsEmbedBase {
	VipsConversion parent_instance;

	/* The input image.
	 */
	VipsImage *in;

	VipsExtend extend;
	VipsArrayDouble *background;
	int width;
	int height;

	/* Pixel we paint calculated from background.
	 */
	VipsPel *ink;

	/* Geometry calculations. 
	 */
	VipsRect rout;		/* Whole output area */
	VipsRect rsub;		/* Rect occupied by image */

	/* The 8 border pieces. The 4 borders strictly up/down/left/right of
	 * the main image, and the 4 corner pieces.
	 */
	VipsRect border[8];

	/* Passed to us by subclasses.
	 */
	int x;
	int y;
} VipsEmbedBase;

typedef VipsConversionClass VipsEmbedBaseClass;

G_DEFINE_ABSTRACT_TYPE( VipsEmbedBase, vips_embed_base, VIPS_TYPE_CONVERSION );

/* r is the bit we are trying to paint, guaranteed to be entirely within
 * border area i. Set out to be the edge of the image we need to paint the
 * pixels in r.
 */
static void
vips_embed_base_find_edge( VipsEmbedBase *base, 
	VipsRect *r, int i, VipsRect *out )
{
	/* Expand the border by 1 pixel, intersect with the image area, and we
	 * get the edge. Usually too much though: eg. we could make the entire
	 * right edge.
	 */
	*out = base->border[i];
	vips_rect_marginadjust( out, 1 );
	vips_rect_intersectrect( out, &base->rsub, out );

	/* Usually too much though: eg. we could make the entire
	 * right edge. If we're strictly up/down/left/right of the image, we
	 * can trim.
	 */
	if( i == 0 || 
		i == 2 ) {
		VipsRect extend;

		/* Above or below.
		 */
		extend = *r;
		extend.top = 0;
		extend.height = base->height;
		vips_rect_intersectrect( out, &extend, out );
	}
	if( i == 1 || 
		i == 3 ) {
		VipsRect extend;

		/* Left or right.
		 */
		extend = *r;
		extend.left = 0;
		extend.width = base->width;
		vips_rect_intersectrect( out, &extend, out );
	}
}

/* Copy a single pixel sideways into a line of pixels.
 */
static void
vips_embed_base_copy_pixel( VipsEmbedBase *base, 
	VipsPel *q, VipsPel *p, int n )
{
	const int bs = VIPS_IMAGE_SIZEOF_PEL( base->in );

	int x, b;

	for( x = 0; x < n; x++ )
		for( b = 0; b < bs; b++ )
			*q++ = p[b];
}

/* Paint r of region or. It's a border area, lying entirely within 
 * base->border[i]. p points to the top-left source pixel to fill with. 
 * plsk is the line stride.
 */
static void
vips_embed_base_paint_edge( VipsEmbedBase *base, 
	VipsRegion *or, int i, VipsRect *r, VipsPel *p, int plsk )
{
	const int bs = VIPS_IMAGE_SIZEOF_PEL( base->in );

	VipsRect todo;
	VipsPel *q;
	int y;

	VIPS_GATE_START( "vips_embed_base_paint_edge: work" );

	/* Pixels left to paint.
	 */
	todo = *r;

	/* Corner pieces ... copy the single pixel to paint the top line of
	 * todo, then use the line copier below to paint the rest of it.
	 */
	if( i > 3 ) {
		q = VIPS_REGION_ADDR( or, todo.left, todo.top );
		vips_embed_base_copy_pixel( base, q, p, todo.width );

		p = q;
		todo.top += 1;
		todo.height -= 1;
	}

	if( i == 1 || i == 3 ) {
		/* Vertical line of pixels to copy.
		 */
		for( y = 0; y < todo.height; y++ ) {
			q = VIPS_REGION_ADDR( or, todo.left, todo.top + y );
			vips_embed_base_copy_pixel( base, q, p, todo.width );
			p += plsk;
		}
	}
	else {
		/* Horizontal line of pixels to copy.
		 */
		for( y = 0; y < todo.height; y++ ) {
			q = VIPS_REGION_ADDR( or, todo.left, todo.top + y );
			memcpy( q, p, bs * todo.width );
		}
	}

	VIPS_GATE_STOP( "vips_embed_base_paint_edge: work" );
}

static int
vips_embed_base_gen( VipsRegion *or, 
	void *seq, void *a, void *b, gboolean *stop )
{
	VipsRegion *ir = (VipsRegion *) seq;
	VipsEmbedBase *base = (VipsEmbedBase *) b;
	VipsRect *r = &or->valid;

	VipsRect ovl;
	int i;
	VipsPel *p;
	int plsk;

	/* Entirely within the input image? Generate the subimage and copy
	 * pointers.
	 */
	if( vips_rect_includesrect( &base->rsub, r ) ) {
		VipsRect need;

		need = *r;
		need.left -= base->x;
		need.top -= base->y;
		if( vips_region_prepare( ir, &need ) ||
			vips_region_region( or, ir, r, need.left, need.top ) )
			return( -1 );

		return( 0 );
	}

	/* Does any of the input image appear in the area we have been asked 
	 * to make? Paste it in.
	 */
	vips_rect_intersectrect( r, &base->rsub, &ovl );
	if( !vips_rect_isempty( &ovl ) ) {
		/* Paint the bits coming from the input image.
		 */
		ovl.left -= base->x;
		ovl.top -= base->y;
		if( vips_region_prepare_to( ir, or, &ovl, 
			ovl.left + base->x, ovl.top + base->y ) )
			return( -1 );
		ovl.left += base->x;
		ovl.top += base->y;
	}

	switch( base->extend ) {
	case VIPS_EXTEND_BLACK:
	case VIPS_EXTEND_WHITE:
		VIPS_GATE_START( "vips_embed_base_gen: work1" );

		/* Paint the borders a solid value.
		 */
		for( i = 0; i < 8; i++ )
			vips_region_paint( or, &base->border[i], 
				base->extend == 0 ? 0 : 255 );

		VIPS_GATE_STOP( "vips_embed_base_gen: work1" );

		break;

	case VIPS_EXTEND_BACKGROUND:
		VIPS_GATE_START( "vips_embed_base_gen: work2" );

		/* Paint the borders a solid value.
		 */
		for( i = 0; i < 8; i++ )
			vips_region_paint_pel( or, &base->border[i], base->ink ); 

		VIPS_GATE_STOP( "vips_embed_base_gen: work2" );

		break;

	case VIPS_EXTEND_COPY:
		/* Extend the borders.
		 */
		for( i = 0; i < 8; i++ ) {
			VipsRect todo;
			VipsRect edge;

			vips_rect_intersectrect( r, &base->border[i], &todo );
			if( !vips_rect_isempty( &todo ) ) {
				vips_embed_base_find_edge( base, 
					&todo, i, &edge );

				/* Did we paint any of the input image? If we
				 * did, we can fetch the edge pixels from
				 * that.
				 */
				if( !vips_rect_isempty( &ovl ) ) {
					p = VIPS_REGION_ADDR( or, 
						edge.left, edge.top );
					plsk = VIPS_REGION_LSKIP( or );
				}
				else {
					/* No pixels painted ... fetch
					 * directly from the input image.
					 */
					edge.left -= base->x;
					edge.top -= base->y;
					if( vips_region_prepare( ir, &edge ) )
						return( -1 );
					p = VIPS_REGION_ADDR( ir,
						 edge.left, edge.top );
					plsk = VIPS_REGION_LSKIP( ir );
				}

				vips_embed_base_paint_edge( base, 
					or, i, &todo, p, plsk );
			}
		}

		break;

	default:	
		g_assert_not_reached();
	}

	return( 0 );
}

static int
vips_embed_base_build( VipsObject *object )
{
	VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );
	VipsConversion *conversion = VIPS_CONVERSION( object );
	VipsEmbedBase *base = (VipsEmbedBase *) object;
	VipsImage **t = (VipsImage **) vips_object_local_array( object, 7 );

	VipsRect want;

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

	/* nip2 can generate this quite often ... just copy.
	 */
	if( base->x == 0 && 
		base->y == 0 && 
		base->width == base->in->Xsize && 
		base->height == base->in->Ysize )
		return( vips_image_write( base->in, conversion->out ) );

	if( !vips_object_argument_isset( object, "extend" ) &&
		vips_object_argument_isset( object, "background" ) )
		base->extend = VIPS_EXTEND_BACKGROUND; 

	if( base->extend == VIPS_EXTEND_BACKGROUND ) 
		if( !(base->ink = vips__vector_to_ink( 
			class->nickname, base->in,
			VIPS_AREA( base->background )->data, NULL, 
			VIPS_AREA( base->background )->n )) )
			return( -1 );

	switch( base->extend ) {
	case VIPS_EXTEND_REPEAT:
{
		/* Clock arithmetic: we want negative x/y to wrap around
		 * nicely.
		 */
		const int nx = base->x < 0 ?
			-base->x % base->in->Xsize : 
			base->in->Xsize - base->x % base->in->Xsize;
		const int ny = base->y < 0 ?
			-base->y % base->in->Ysize : 
			base->in->Ysize - base->y % base->in->Ysize;

		if( vips_replicate( base->in, &t[0],
			base->width / base->in->Xsize + 2, 
			base->height / base->in->Ysize + 2, NULL ) ||
			vips_extract_area( t[0], &t[1], 
				nx, ny, base->width, base->height, NULL ) || 
			vips_image_write( t[1], conversion->out ) )
			return( -1 );

}
		break;

	case VIPS_EXTEND_MIRROR:
{
		/* As repeat, but the tiles are twice the size because of
		 * mirroring.
		 */
		const int w2 = base->in->Xsize * 2;
		const int h2 = base->in->Ysize * 2;

		const int nx = base->x < 0 ? -base->x % w2 : w2 - base->x % w2;
		const int ny = base->y < 0 ? -base->y % h2 : h2 - base->y % h2;

		if( 
			/* Make a 2x2 mirror tile.
			 */
			vips_flip( base->in, &t[0], 
				VIPS_DIRECTION_HORIZONTAL, NULL ) ||
			vips_join( base->in, t[0], &t[1], 
				VIPS_DIRECTION_HORIZONTAL, NULL ) ||
			vips_flip( t[1], &t[2], 
				VIPS_DIRECTION_VERTICAL, NULL ) ||
			vips_join( t[1], t[2], &t[3], 
				VIPS_DIRECTION_VERTICAL, NULL ) ||

			/* Repeat, then cut out the centre.
			 */
			vips_replicate( t[3], &t[4], 
				base->width / t[3]->Xsize + 2, 
				base->height / t[3]->Ysize + 2, NULL ) ||
			vips_extract_area( t[4], &t[5], 
				nx, ny, base->width, base->height, NULL ) ||

			/* Overwrite the centre with the in, much faster
			 * for centre pixels.
			 */
			vips_insert( t[5], base->in, &t[6], 
				base->x, base->y, NULL ) ||

			vips_image_write( t[6], conversion->out ) )
				return( -1 );
}
		break;

	case VIPS_EXTEND_BLACK:
	case VIPS_EXTEND_WHITE:
	case VIPS_EXTEND_BACKGROUND:
	case VIPS_EXTEND_COPY:
		/* embed is used in many places. We don't really care about
		 * geometry, so use ANY to avoid disturbing all pipelines. 
		 */
		if( vips_image_pipelinev( conversion->out, 
			VIPS_DEMAND_STYLE_ANY, base->in, NULL ) )
			return( -1 );

		conversion->out->Xsize = base->width;
		conversion->out->Ysize = base->height;

		/* Whole output area.
		 */
		base->rout.left = 0;
		base->rout.top = 0;
		base->rout.width = conversion->out->Xsize;
		base->rout.height = conversion->out->Ysize;

		/* Rect occupied by image (can be clipped to nothing).
		 */
		want.left = base->x;
		want.top = base->y;
		want.width = base->in->Xsize;
		want.height = base->in->Ysize;
		vips_rect_intersectrect( &want, &base->rout, &base->rsub );

		/* FIXME ... actually, it can't. base_find_edge() will fail 
		 * if rsub is empty. Make this more general at some point 
		 * and remove this test.
		 */
		if( vips_rect_isempty( &base->rsub ) ) {
			vips_error( class->nickname, 
				"%s", _( "bad dimensions" ) );
			return( -1 );
		}

		/* Edge rects of new pixels ... top, right, bottom, left. Order
		 * important. Can be empty.
		 */
		base->border[0].left = base->rsub.left;
		base->border[0].top = 0;
		base->border[0].width = base->rsub.width;
		base->border[0].height = base->rsub.top;

		base->border[1].left = VIPS_RECT_RIGHT( &base->rsub );
		base->border[1].top = base->rsub.top;
		base->border[1].width = conversion->out->Xsize - 
			VIPS_RECT_RIGHT( &base->rsub );
		base->border[1].height = base->rsub.height;

		base->border[2].left = base->rsub.left;	
		base->border[2].top = VIPS_RECT_BOTTOM( &base->rsub );
		base->border[2].width = base->rsub.width;
		base->border[2].height = conversion->out->Ysize - 
			VIPS_RECT_BOTTOM( &base->rsub );

		base->border[3].left = 0;	
		base->border[3].top = base->rsub.top;
		base->border[3].width = base->rsub.left;
		base->border[3].height = base->rsub.height;

		/* Corner rects. Top-left, top-right, bottom-right, 
		 * bottom-left. Order important.
		 */
		base->border[4].left = 0;
		base->border[4].top = 0;
		base->border[4].width = base->rsub.left;
		base->border[4].height = base->rsub.top;

		base->border[5].left = VIPS_RECT_RIGHT( &base->rsub );
		base->border[5].top = 0;
		base->border[5].width = conversion->out->Xsize - 
			VIPS_RECT_RIGHT( &base->rsub );
		base->border[5].height = base->rsub.top;

		base->border[6].left = VIPS_RECT_RIGHT( &base->rsub );
		base->border[6].top = VIPS_RECT_BOTTOM( &base->rsub );
		base->border[6].width = conversion->out->Xsize - 
			VIPS_RECT_RIGHT( &base->rsub );
		base->border[6].height = conversion->out->Ysize - 
			VIPS_RECT_BOTTOM( &base->rsub );

		base->border[7].left = 0;
		base->border[7].top = VIPS_RECT_BOTTOM( &base->rsub );
		base->border[7].width = base->rsub.left;
		base->border[7].height = conversion->out->Ysize - 
			VIPS_RECT_BOTTOM( &base->rsub );

		if( vips_image_generate( conversion->out,
			vips_start_one, vips_embed_base_gen, vips_stop_one, 
			base->in, base ) )
			return( -1 );

		break;

	default:
		g_assert_not_reached();
	}

	return( 0 );
}

static void
vips_embed_base_class_init( VipsEmbedBaseClass *class )
{
	GObjectClass *gobject_class = G_OBJECT_CLASS( class );
	VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( class );

	VIPS_DEBUG_MSG( "vips_embed_base_class_init\n" );

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

	vobject_class->nickname = "embed_base";
	vobject_class->description = _( "embed an image in a larger image" );
	vobject_class->build = vips_embed_base_build;

	/* Not seq with mirror.
	 */

	VIPS_ARG_IMAGE( class, "in", 1, 
		_( "Input" ), 
		_( "Input image" ),
		VIPS_ARGUMENT_REQUIRED_INPUT,
		G_STRUCT_OFFSET( VipsEmbedBase, in ) );

	VIPS_ARG_INT( class, "width", 5, 
		_( "Width" ), 
		_( "Image width in pixels" ),
		VIPS_ARGUMENT_REQUIRED_INPUT,
		G_STRUCT_OFFSET( VipsEmbedBase, width ),
		1, 1000000000, 1 );

	VIPS_ARG_INT( class, "height", 6, 
		_( "Height" ), 
		_( "Image height in pixels" ),
		VIPS_ARGUMENT_REQUIRED_INPUT,
		G_STRUCT_OFFSET( VipsEmbedBase, height ),
		1, 1000000000, 1 );

	VIPS_ARG_ENUM( class, "extend", 7, 
		_( "Extend" ), 
		_( "How to generate the extra pixels" ),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET( VipsEmbedBase, extend ),
		VIPS_TYPE_EXTEND, VIPS_EXTEND_BLACK );

	VIPS_ARG_BOXED( class, "background", 12, 
		_( "Background" ), 
		_( "Color for background pixels" ),
		VIPS_ARGUMENT_OPTIONAL_INPUT,
		G_STRUCT_OFFSET( VipsEmbedBase, background ),
		VIPS_TYPE_ARRAY_DOUBLE );

}

static void
vips_embed_base_init( VipsEmbedBase *base )
{
	base->extend = VIPS_EXTEND_BLACK;
	base->background = vips_array_double_newv( 1, 0.0 );
}

/* Embed with specified x, y
 */

typedef struct _VipsEmbed {
	VipsEmbedBase parent_instance;

	int x;
	int y;
} VipsEmbed;

typedef VipsConversionClass VipsEmbedClass;

G_DEFINE_TYPE( VipsEmbed, vips_embed, vips_embed_base_get_type() );

static int
vips_embed_build( VipsObject *object )
{
	VipsEmbedBase *base = (VipsEmbedBase *) object;
	VipsEmbed *embed = (VipsEmbed *) object;

	/* Just pass the specified x, y down.
	 */
	base->x = embed->x;
	base->y = embed->y;

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

	return( 0 );
}

static void
vips_embed_class_init( VipsEmbedClass *class )
{
	GObjectClass *gobject_class = G_OBJECT_CLASS( class );
	VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( class );

	VIPS_DEBUG_MSG( "vips_embed_class_init\n" );

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

	vobject_class->nickname = "embed";
	vobject_class->description = _( "embed an image in a larger image" );
	vobject_class->build = vips_embed_build;

	VIPS_ARG_INT( class, "x", 3, 
		_( "x" ), 
		_( "Left edge of input in output" ),
		VIPS_ARGUMENT_REQUIRED_INPUT,
		G_STRUCT_OFFSET( VipsEmbed, x ),
		-1000000000, 1000000000, 0 );

	VIPS_ARG_INT( class, "y", 4, 
		_( "y" ), 
		_( "Top edge of input in output" ),
		VIPS_ARGUMENT_REQUIRED_INPUT,
		G_STRUCT_OFFSET( VipsEmbed, y ),
		-1000000000, 1000000000, 0 );

}

static void
vips_embed_init( VipsEmbed *embed )
{
}

/**
 * vips_embed: (method)
 * @in: input image
 * @out: (out): output image
 * @x: place @in at this x position in @out
 * @y: place @in at this y position in @out
 * @width: @out should be this many pixels across
 * @height: @out should be this many pixels down
 * @...: %NULL-terminated list of optional named arguments
 *
 * Optional arguments:
 *
 * * @extend: #VipsExtend to generate the edge pixels (default: black)
 * * @background: #VipsArrayDouble colour for edge pixels
 *
 * The opposite of vips_extract_area(): embed @in within an image of size 
 * @width by @height at position @x, @y.  
 *
 * @extend
 * controls what appears in the new pels, see #VipsExtend. 
 *
 * See also: vips_extract_area(), vips_insert().
 * 
 * Returns: 0 on success, -1 on error.
 */
int
vips_embed( VipsImage *in, VipsImage **out, 
	int x, int y, int width, int height, ... )
{
	va_list ap;
	int result;

	va_start( ap, height );
	result = vips_call_split( "embed", ap, in, out, x, y, width, height );
	va_end( ap );

	return( result );
}

/* Embed with a general direction.
 */

typedef struct _VipsGravity {
	VipsEmbedBase parent_instance;

	VipsCompassDirection direction;
} VipsGravity;

typedef VipsConversionClass VipsGravityClass;

G_DEFINE_TYPE( VipsGravity, vips_gravity, vips_embed_base_get_type() );

static int
vips_gravity_build( VipsObject *object )
{
	VipsEmbedBase *base = (VipsEmbedBase *) object;
	VipsGravity *gravity = (VipsGravity *) object;

	if( vips_object_argument_isset( object, "in" ) &&
		vips_object_argument_isset( object, "width" ) &&
		vips_object_argument_isset( object, "height" ) &&
		vips_object_argument_isset( object, "direction" ) ) {
		switch( gravity->direction ) {
		case VIPS_COMPASS_DIRECTION_CENTRE:
			base->x = (base->width - base->in->Xsize) / 2;
			base->y = (base->height - base->in->Ysize) / 2;
			break;

		case VIPS_COMPASS_DIRECTION_NORTH:
			base->x = (base->width - base->in->Xsize) / 2;
			base->y = 0;
			break;

		case VIPS_COMPASS_DIRECTION_EAST:
			base->x = base->width - base->in->Xsize;
			base->y = (base->height - base->in->Ysize) / 2;
			break;

		case VIPS_COMPASS_DIRECTION_SOUTH:
			base->x = (base->width - base->in->Xsize) / 2;
			base->y = base->height - base->in->Ysize;
			break;

		case VIPS_COMPASS_DIRECTION_WEST:
			base->x = 0;
			base->y = (base->height - base->in->Ysize) / 2;
			break;

		case VIPS_COMPASS_DIRECTION_NORTH_EAST:
			base->x = base->width - base->in->Xsize;
			base->y = 0;
			break;

		case VIPS_COMPASS_DIRECTION_SOUTH_EAST:
			base->x = base->width - base->in->Xsize;
			base->y = base->height - base->in->Ysize;
			break;

		case VIPS_COMPASS_DIRECTION_SOUTH_WEST:
			base->x = 0;
			base->y = base->height - base->in->Ysize;
			break;

		case VIPS_COMPASS_DIRECTION_NORTH_WEST:
			base->x = 0;
			base->y = 0;
			break;

		default:
			g_assert_not_reached();

		}
	}

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

	return( 0 );
}

static void
vips_gravity_class_init( VipsGravityClass *class )
{
	GObjectClass *gobject_class = G_OBJECT_CLASS( class );
	VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( class );

	VIPS_DEBUG_MSG( "vips_gravity_class_init\n" );

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

	vobject_class->nickname = "gravity";
	vobject_class->description = _( "place an image within a larger "
		"image with a certain gravity" );
	vobject_class->build = vips_gravity_build;

	VIPS_ARG_ENUM( class, "direction", 3, 
		_( "Direction" ), 
		_( "direction to place image within width/height" ),
		VIPS_ARGUMENT_REQUIRED_INPUT,
		G_STRUCT_OFFSET( VipsGravity, direction ),
		VIPS_TYPE_COMPASS_DIRECTION, VIPS_COMPASS_DIRECTION_CENTRE );
}

static void
vips_gravity_init( VipsGravity *gravity )
{
	gravity->direction = VIPS_COMPASS_DIRECTION_CENTRE;
}

/**
 * vips_gravity:
 * @in: input image
 * @out: output image
 * @direction: place @in at this direction in @out
 * @width: @out should be this many pixels across
 * @height: @out should be this many pixels down
 * @...: %NULL-terminated list of optional named arguments
 *
 * Optional arguments:
 *
 * * @extend: #VipsExtend to generate the edge pixels (default: black)
 * * @background: #VipsArrayDouble colour for edge pixels
 *
 * The opposite of vips_extract_area(): place @in within an image of size 
 * @width by @height at a certain gravity. 
 *
 * @extend
 * controls what appears in the new pels, see #VipsExtend. 
 *
 * See also: vips_extract_area(), vips_insert().
 * 
 * Returns: 0 on success, -1 on error.
 */
int
vips_gravity( VipsImage *in, VipsImage **out, 
	VipsCompassDirection direction, int width, int height, ... )
{
	va_list ap;
	int result;

	va_start( ap, height );
	result = vips_call_split( "gravity", ap, in, out, 
		direction, width, height );
	va_end( ap );

	return( result );
}
