/* 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 #endif /*HAVE_CONFIG_H*/ #include #include #include #include #include #include #include #include #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 ); }