444 lines
11 KiB
C
444 lines
11 KiB
C
/* im_zoom
|
|
*
|
|
* Author: N. Martinez 1991
|
|
* 6/6/94 JC
|
|
* - rewritten to ANSI-C
|
|
* - now works for any type, including IM_CODING_LABQ
|
|
* 7/10/94 JC
|
|
* - new IM_ARRAY() macro
|
|
* 26/1/96 JC
|
|
* - separate x and y zoom factors
|
|
* 21/8/96 JC
|
|
* - partial, yuk! this is so complicated ...
|
|
* 30/8/96 JC
|
|
* - sets demand_hint
|
|
* 10/2/00 JC
|
|
* - check for integer overflow in zoom facs ... was happening with ip's
|
|
* zoom on large images
|
|
* 3/8/02 JC
|
|
* - fall back to im_copy() for x & y factors == 1
|
|
* 24/3/09
|
|
* - added IM_CODING_RAD support
|
|
* 1/2/10
|
|
* - gtkdoc
|
|
* 1/6/13
|
|
* - redo as a class
|
|
*/
|
|
|
|
/*
|
|
|
|
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
|
|
|
|
*/
|
|
|
|
/*
|
|
* TODO:
|
|
* Test for pixel size and use memcpy() on individual pixels once they reach
|
|
* sizes of the order of tens of bytes. char-wise copy is quicker than
|
|
* memcpy() for smaller pixels.
|
|
*
|
|
* Also, I haven't tested it but int-wise copying may be faster still, as
|
|
* long as alignment permits it.
|
|
*
|
|
* tcv. 2006-09-01
|
|
*/
|
|
|
|
/* Turn on ADDR() range checks.
|
|
#define DEBUG 1
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif /*HAVE_CONFIG_H*/
|
|
#include <vips/intl.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <limits.h>
|
|
|
|
#include <vips/vips.h>
|
|
|
|
#include "pconversion.h"
|
|
|
|
typedef struct _VipsZoom {
|
|
VipsConversion parent_instance;
|
|
|
|
/* The input image.
|
|
*/
|
|
VipsImage *in;
|
|
|
|
int xfac; /* Scale factors */
|
|
int yfac;
|
|
|
|
} VipsZoom;
|
|
|
|
typedef VipsConversionClass VipsZoomClass;
|
|
|
|
G_DEFINE_TYPE( VipsZoom, vips_zoom, VIPS_TYPE_CONVERSION );
|
|
|
|
/* Round N down to P boundary.
|
|
*/
|
|
#define ROUND_DOWN( N, P ) ((N) - ((N) % P))
|
|
|
|
/* Round N up to P boundary.
|
|
*/
|
|
#define ROUND_UP( N, P ) (ROUND_DOWN( (N) + (P) - 1, (P) ))
|
|
|
|
/* Paint the part of the region containing only whole pels.
|
|
*/
|
|
static void
|
|
vips_zoom_paint_whole( VipsRegion *or, VipsRegion *ir, VipsZoom *zoom,
|
|
const int left, const int right, const int top, const int bottom )
|
|
{
|
|
const int ps = VIPS_IMAGE_SIZEOF_PEL( ir->im );
|
|
const int ls = VIPS_REGION_LSKIP( or );
|
|
const int rs = ps * (right - left);
|
|
|
|
/* Transform to ir coordinates.
|
|
*/
|
|
const int ileft = left / zoom->xfac;
|
|
const int iright = right / zoom->xfac;
|
|
const int itop = top / zoom->yfac;
|
|
const int ibottom = bottom / zoom->yfac;
|
|
|
|
int x, y, z, i;
|
|
|
|
/* We know this!
|
|
*/
|
|
g_assert( right > left && bottom > top &&
|
|
right % zoom->xfac == 0 &&
|
|
left % zoom->xfac == 0 &&
|
|
top % zoom->yfac == 0 &&
|
|
bottom % zoom->yfac == 0 );
|
|
|
|
/* Loop over input, as we know we are all whole.
|
|
*/
|
|
for( y = itop; y < ibottom; y++ ) {
|
|
VipsPel *p = VIPS_REGION_ADDR( ir, ileft, y );
|
|
VipsPel *q = VIPS_REGION_ADDR( or, left, y * zoom->yfac );
|
|
VipsPel *r;
|
|
|
|
/* Expand the first line of pels.
|
|
*/
|
|
r = q;
|
|
for( x = ileft; x < iright; x++ ) {
|
|
/* Copy each pel xfac times.
|
|
*/
|
|
for( z = 0; z < zoom->xfac; z++ ) {
|
|
for( i = 0; i < ps; i++ )
|
|
r[i] = p[i];
|
|
|
|
r += ps;
|
|
}
|
|
|
|
p += ps;
|
|
}
|
|
|
|
/* Copy the expanded line yfac-1 times.
|
|
*/
|
|
r = q + ls;
|
|
for( z = 1; z < zoom->yfac; z++ ) {
|
|
memcpy( r, q, rs );
|
|
r += ls;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Paint the part of the region containing only part-pels.
|
|
*/
|
|
static void
|
|
vips_zoom_paint_part( VipsRegion *or, VipsRegion *ir, VipsZoom *zoom,
|
|
const int left, const int right, const int top, const int bottom )
|
|
{
|
|
const int ps = VIPS_IMAGE_SIZEOF_PEL( ir->im );
|
|
const int ls = VIPS_REGION_LSKIP( or );
|
|
const int rs = ps * (right - left);
|
|
|
|
/* Start position in input.
|
|
*/
|
|
const int ix = left / zoom->xfac;
|
|
const int iy = top / zoom->yfac;
|
|
|
|
/* Pels down to yfac boundary, pels down to bottom. Do the smallest of
|
|
* these for first y loop.
|
|
*/
|
|
const int ptbound = (iy + 1) * zoom->yfac - top;
|
|
const int ptbot = bottom - top;
|
|
|
|
int yt = VIPS_MIN( ptbound, ptbot );
|
|
|
|
int x, y, z, i;
|
|
|
|
/* Only know this.
|
|
*/
|
|
g_assert( right - left >= 0 && bottom - top >= 0 );
|
|
|
|
/* Have to loop over output.
|
|
*/
|
|
for( y = top; y < bottom; ) {
|
|
VipsPel *p = VIPS_REGION_ADDR( ir, ix, y / zoom->yfac );
|
|
VipsPel *q = VIPS_REGION_ADDR( or, left, y );
|
|
VipsPel *r;
|
|
|
|
/* Output pels until we jump the input pointer.
|
|
*/
|
|
int xt = (ix + 1) * zoom->xfac - left;
|
|
|
|
/* Loop for this output line.
|
|
*/
|
|
r = q;
|
|
for( x = left; x < right; x++ ) {
|
|
/* Copy 1 pel.
|
|
*/
|
|
for( i = 0; i < ps; i++ )
|
|
r[i] = p[i];
|
|
r += ps;
|
|
|
|
/* Move input if on boundary.
|
|
*/
|
|
--xt;
|
|
if( xt == 0 ) {
|
|
xt = zoom->xfac;
|
|
p += ps;
|
|
}
|
|
}
|
|
|
|
/* Repeat that output line until the bottom of this pixel
|
|
* boundary, or we hit bottom.
|
|
*/
|
|
r = q + ls;
|
|
for( z = 1; z < yt; z++ ) {
|
|
memcpy( r, q, rs );
|
|
r += ls;
|
|
}
|
|
|
|
/* Move y on by the number of lines we wrote.
|
|
*/
|
|
y += yt;
|
|
|
|
/* Reset yt for next iteration.
|
|
*/
|
|
yt = zoom->yfac;
|
|
}
|
|
}
|
|
|
|
/* Zoom a VipsRegion.
|
|
*/
|
|
static int
|
|
vips_zoom_gen( VipsRegion *or, void *seq, void *a, void *b, gboolean *stop )
|
|
{
|
|
VipsRegion *ir = (VipsRegion *) seq;
|
|
VipsZoom *zoom = (VipsZoom *) b;
|
|
|
|
/* Output area we are building.
|
|
*/
|
|
const VipsRect *r = &or->valid;
|
|
const int ri = VIPS_RECT_RIGHT( r );
|
|
const int bo = VIPS_RECT_BOTTOM(r);
|
|
|
|
VipsRect s;
|
|
int left, right, top, bottom;
|
|
int width, height;
|
|
|
|
/* Area of input we need. We have to round out, as we may have
|
|
* part-pixels all around the edges.
|
|
*/
|
|
left = ROUND_DOWN( r->left, zoom->xfac );
|
|
right = ROUND_UP( ri, zoom->xfac );
|
|
top = ROUND_DOWN( r->top, zoom->yfac );
|
|
bottom = ROUND_UP( bo, zoom->yfac );
|
|
width = right - left;
|
|
height = bottom - top;
|
|
s.left = left / zoom->xfac;
|
|
s.top = top / zoom->yfac;
|
|
s.width = width / zoom->xfac;
|
|
s.height = height / zoom->yfac;
|
|
if( vips_region_prepare( ir, &s ) )
|
|
return( -1 );
|
|
|
|
/* Find the part of the output (if any) which uses only whole pels.
|
|
*/
|
|
left = ROUND_UP( r->left, zoom->xfac );
|
|
right = ROUND_DOWN( ri, zoom->xfac );
|
|
top = ROUND_UP( r->top, zoom->yfac );
|
|
bottom = ROUND_DOWN( bo, zoom->yfac );
|
|
width = right - left;
|
|
height = bottom - top;
|
|
|
|
/* Stage 1: we just paint the whole pels in the centre of the region.
|
|
* As we know they are not clipped, we can do it quickly.
|
|
*/
|
|
if( width > 0 && height > 0 )
|
|
vips_zoom_paint_whole( or, ir, zoom, left, right, top, bottom );
|
|
|
|
/* Just fractional pixels left. Paint in the top, left, right and
|
|
* bottom parts.
|
|
*/
|
|
if( top - r->top > 0 )
|
|
/* Some top pixels.
|
|
*/
|
|
vips_zoom_paint_part( or, ir, zoom,
|
|
r->left, ri, r->top, VIPS_MIN( top, bo ) );
|
|
if( left - r->left > 0 && height > 0 )
|
|
/* Left pixels.
|
|
*/
|
|
vips_zoom_paint_part( or, ir, zoom,
|
|
r->left, VIPS_MIN( left, ri ), top, bottom );
|
|
if( ri - right > 0 && height > 0 )
|
|
/* Right pixels.
|
|
*/
|
|
vips_zoom_paint_part( or, ir, zoom,
|
|
VIPS_MAX( right, r->left ), ri, top, bottom );
|
|
if( bo - bottom > 0 && height >= 0 )
|
|
/* Bottom pixels.
|
|
*/
|
|
vips_zoom_paint_part( or, ir, zoom,
|
|
r->left, ri, VIPS_MAX( bottom, r->top ), bo );
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
static int
|
|
vips_zoom_build( VipsObject *object )
|
|
{
|
|
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );
|
|
VipsConversion *conversion = VIPS_CONVERSION( object );
|
|
VipsZoom *zoom = (VipsZoom *) object;
|
|
|
|
if( VIPS_OBJECT_CLASS( vips_zoom_parent_class )->build( object ) )
|
|
return( -1 );
|
|
|
|
g_assert( zoom->xfac > 0 );
|
|
g_assert( zoom->yfac > 0 );
|
|
|
|
/* Make sure we won't get integer overflow.
|
|
*/
|
|
if( (double) zoom->in->Xsize * zoom->xfac > (double) INT_MAX / 2 ||
|
|
(double) zoom->in->Ysize * zoom->yfac > (double) INT_MAX / 2 ) {
|
|
vips_error( class->nickname,
|
|
"%s", _( "zoom factors too large" ) );
|
|
return( -1 );
|
|
}
|
|
if( zoom->xfac == 1 &&
|
|
zoom->yfac == 1 )
|
|
return( vips_image_write( zoom->in, conversion->out ) );
|
|
|
|
if( vips_image_pio_input( zoom->in ) ||
|
|
vips_check_coding_known( class->nickname, zoom->in ) )
|
|
return( -1 );
|
|
|
|
/* Set demand hints. THINSTRIP will prevent us from using
|
|
* vips_zoom_paint_whole() much ... so go for FATSTRIP.
|
|
*/
|
|
if( vips_image_pipelinev( conversion->out,
|
|
VIPS_DEMAND_STYLE_FATSTRIP, zoom->in, NULL ) )
|
|
return( -1 );
|
|
conversion->out->Xsize = zoom->in->Xsize * zoom->xfac;
|
|
conversion->out->Ysize = zoom->in->Ysize * zoom->yfac;
|
|
|
|
if( vips_image_generate( conversion->out,
|
|
vips_start_one, vips_zoom_gen, vips_stop_one,
|
|
zoom->in, zoom ) )
|
|
return( -1 );
|
|
|
|
return( 0 );
|
|
}
|
|
|
|
/* xy range we sanity check on ... just to stop crazy numbers from divide by 0
|
|
* etc. causing g_assert() failures later.
|
|
*/
|
|
#define RANGE (100000000)
|
|
|
|
static void
|
|
vips_zoom_class_init( VipsZoomClass *class )
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS( class );
|
|
VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( class );
|
|
VipsOperationClass *operation_class = VIPS_OPERATION_CLASS( class );
|
|
|
|
gobject_class->set_property = vips_object_set_property;
|
|
gobject_class->get_property = vips_object_get_property;
|
|
|
|
vobject_class->nickname = "zoom";
|
|
vobject_class->description = _( "zoom an image" );
|
|
vobject_class->build = vips_zoom_build;
|
|
|
|
operation_class->flags = VIPS_OPERATION_SEQUENTIAL_UNBUFFERED;
|
|
|
|
VIPS_ARG_IMAGE( class, "input", 0,
|
|
_( "Input" ),
|
|
_( "Input image" ),
|
|
VIPS_ARGUMENT_REQUIRED_INPUT,
|
|
G_STRUCT_OFFSET( VipsZoom, in ) );
|
|
|
|
VIPS_ARG_INT( class, "xfac", 2,
|
|
_( "Xfac" ),
|
|
_( "Horizontal zoom factor" ),
|
|
VIPS_ARGUMENT_REQUIRED_INPUT,
|
|
G_STRUCT_OFFSET( VipsZoom, xfac ),
|
|
1, RANGE, 1 );
|
|
|
|
VIPS_ARG_INT( class, "yfac", 3,
|
|
_( "Yfac" ),
|
|
_( "Vertical zoom factor" ),
|
|
VIPS_ARGUMENT_REQUIRED_INPUT,
|
|
G_STRUCT_OFFSET( VipsZoom, yfac ),
|
|
1, RANGE, 1 );
|
|
|
|
}
|
|
|
|
static void
|
|
vips_zoom_init( VipsZoom *zoom )
|
|
{
|
|
}
|
|
|
|
/**
|
|
* vips_zoom:
|
|
* @in: input image
|
|
* @out: output image
|
|
* @xfac: horizontal scale factor
|
|
* @yfac: vertical scale factor
|
|
* @...: %NULL-terminated list of optional named arguments
|
|
*
|
|
* Zoom an image by repeating pixels. This is fast nearest-neighbour
|
|
* zoom.
|
|
*
|
|
* See also: vips_affine(), vips_subsample().
|
|
*
|
|
* Returns: 0 on success, -1 on error.
|
|
*/
|
|
int
|
|
vips_zoom( VipsImage *in, VipsImage **out, int xfac, int yfac, ... )
|
|
{
|
|
va_list ap;
|
|
int result;
|
|
|
|
va_start( ap, yfac );
|
|
result = vips_call_split( "zoom", ap, in, out, xfac, yfac );
|
|
va_end( ap );
|
|
|
|
return( result );
|
|
}
|