try a split shrink

not quite done yet
This commit is contained in:
John Cupitt 2015-10-30 18:15:14 +00:00
parent 07ca974273
commit da8f236f95
8 changed files with 1024 additions and 12 deletions

13
TODO
View File

@ -14,6 +14,19 @@
we would then only need to demand the source a line at a time, and the we would then only need to demand the source a line at a time, and the
intermediate image would be much smaller ... possible cache savings as well intermediate image would be much smaller ... possible cache savings as well
added shrink2 for the new code
current state on work machine, debug build
$ time vips shrink wtc.jpg x.v 10 10
memory: high-water mark 238.09 MB
real 0m1.178s
user 0m1.384s
sys 0m0.064s
done h/v, get shrink2 done and bench against plain shrink
- add bandjoinconst ... append a band (or bands?) from a constant - add bandjoinconst ... append a band (or bands?) from a constant
see https://github.com/jcupitt/libvips/issues/325 see https://github.com/jcupitt/libvips/issues/325

View File

@ -25,7 +25,6 @@ TARGET_DIR=$(HTML_DIR)/$(DOC_MODULE)
SETUP_FILES = \ SETUP_FILES = \
$(content_files) \ $(content_files) \
$(expand_content_files) \
$(DOC_MAIN_SGML_FILE) \ $(DOC_MAIN_SGML_FILE) \
$(DOC_MODULE)-sections.txt \ $(DOC_MODULE)-sections.txt \
$(DOC_MODULE)-overrides.txt $(DOC_MODULE)-overrides.txt
@ -87,7 +86,7 @@ GTK_DOC_V_SETUP_0=@echo " DOC Preparing build";
setup-build.stamp: setup-build.stamp:
-$(GTK_DOC_V_SETUP)if test "$(abs_srcdir)" != "$(abs_builddir)" ; then \ -$(GTK_DOC_V_SETUP)if test "$(abs_srcdir)" != "$(abs_builddir)" ; then \
files=`echo $(SETUP_FILES) $(DOC_MODULE).types`; \ files=`echo $(SETUP_FILES) $(expand_content_files) $(DOC_MODULE).types`; \
if test "x$$files" != "x" ; then \ if test "x$$files" != "x" ; then \
for file in $$files ; do \ for file in $$files ; do \
destdir=`dirname $(abs_builddir)/$$file`; \ destdir=`dirname $(abs_builddir)/$$file`; \
@ -119,7 +118,7 @@ scan-build.stamp: setup-build.stamp $(HFILE_GLOB) $(CFILE_GLOB)
$(GTK_DOC_V_INTROSPECT)if grep -l '^..*$$' $(DOC_MODULE).types > /dev/null 2>&1 ; then \ $(GTK_DOC_V_INTROSPECT)if grep -l '^..*$$' $(DOC_MODULE).types > /dev/null 2>&1 ; then \
scanobj_options=""; \ scanobj_options=""; \
gtkdoc-scangobj 2>&1 --help | grep >/dev/null "\-\-verbose"; \ gtkdoc-scangobj 2>&1 --help | grep >/dev/null "\-\-verbose"; \
if test "$$?" = "0"; then \ if test "$(?)" = "0"; then \
if test "x$(V)" = "x1"; then \ if test "x$(V)" = "x1"; then \
scanobj_options="--verbose"; \ scanobj_options="--verbose"; \
fi; \ fi; \
@ -163,17 +162,17 @@ GTK_DOC_V_XREF=$(GTK_DOC_V_XREF_$(V))
GTK_DOC_V_XREF_=$(GTK_DOC_V_XREF_$(AM_DEFAULT_VERBOSITY)) GTK_DOC_V_XREF_=$(GTK_DOC_V_XREF_$(AM_DEFAULT_VERBOSITY))
GTK_DOC_V_XREF_0=@echo " DOC Fixing cross-references"; GTK_DOC_V_XREF_0=@echo " DOC Fixing cross-references";
html-build.stamp: sgml.stamp $(DOC_MAIN_SGML_FILE) $(content_files) $(expand_content_files) html-build.stamp: sgml.stamp $(DOC_MAIN_SGML_FILE) $(content_files)
$(GTK_DOC_V_HTML)rm -rf html && mkdir html && \ $(GTK_DOC_V_HTML)rm -rf html && mkdir html && \
mkhtml_options=""; \ mkhtml_options=""; \
gtkdoc-mkhtml 2>&1 --help | grep >/dev/null "\-\-verbose"; \ gtkdoc-mkhtml 2>&1 --help | grep >/dev/null "\-\-verbose"; \
if test "$$?" = "0"; then \ if test "$(?)" = "0"; then \
if test "x$(V)" = "x1"; then \ if test "x$(V)" = "x1"; then \
mkhtml_options="$$mkhtml_options --verbose"; \ mkhtml_options="$$mkhtml_options --verbose"; \
fi; \ fi; \
fi; \ fi; \
gtkdoc-mkhtml 2>&1 --help | grep >/dev/null "\-\-path"; \ gtkdoc-mkhtml 2>&1 --help | grep >/dev/null "\-\-path"; \
if test "$$?" = "0"; then \ if test "$(?)" = "0"; then \
mkhtml_options="$$mkhtml_options --path=\"$(abs_srcdir)\""; \ mkhtml_options="$$mkhtml_options --path=\"$(abs_srcdir)\""; \
fi; \ fi; \
cd html && gtkdoc-mkhtml $$mkhtml_options $(MKHTML_OPTIONS) $(DOC_MODULE) ../$(DOC_MAIN_SGML_FILE) cd html && gtkdoc-mkhtml $$mkhtml_options $(MKHTML_OPTIONS) $(DOC_MODULE) ../$(DOC_MAIN_SGML_FILE)
@ -195,11 +194,11 @@ GTK_DOC_V_PDF=$(GTK_DOC_V_PDF_$(V))
GTK_DOC_V_PDF_=$(GTK_DOC_V_PDF_$(AM_DEFAULT_VERBOSITY)) GTK_DOC_V_PDF_=$(GTK_DOC_V_PDF_$(AM_DEFAULT_VERBOSITY))
GTK_DOC_V_PDF_0=@echo " DOC Building PDF"; GTK_DOC_V_PDF_0=@echo " DOC Building PDF";
pdf-build.stamp: sgml.stamp $(DOC_MAIN_SGML_FILE) $(content_files) $(expand_content_files) pdf-build.stamp: sgml.stamp $(DOC_MAIN_SGML_FILE) $(content_files)
$(GTK_DOC_V_PDF)rm -f $(DOC_MODULE).pdf && \ $(GTK_DOC_V_PDF)rm -f $(DOC_MODULE).pdf && \
mkpdf_options=""; \ mkpdf_options=""; \
gtkdoc-mkpdf 2>&1 --help | grep >/dev/null "\-\-verbose"; \ gtkdoc-mkpdf 2>&1 --help | grep >/dev/null "\-\-verbose"; \
if test "$$?" = "0"; then \ if test "$(?)" = "0"; then \
if test "x$(V)" = "x1"; then \ if test "x$(V)" = "x1"; then \
mkpdf_options="$$mkpdf_options --verbose"; \ mkpdf_options="$$mkpdf_options --verbose"; \
fi; \ fi; \
@ -224,15 +223,12 @@ clean-local:
@if echo $(SCAN_OPTIONS) | grep -q "\-\-rebuild-types" ; then \ @if echo $(SCAN_OPTIONS) | grep -q "\-\-rebuild-types" ; then \
rm -f $(DOC_MODULE).types; \ rm -f $(DOC_MODULE).types; \
fi fi
@if echo $(SCAN_OPTIONS) | grep -q "\-\-rebuild-sections" ; then \
rm -f $(DOC_MODULE)-sections.txt; \
fi
distclean-local: distclean-local:
@rm -rf xml html $(REPORT_FILES) $(DOC_MODULE).pdf \ @rm -rf xml html $(REPORT_FILES) $(DOC_MODULE).pdf \
$(DOC_MODULE)-decl-list.txt $(DOC_MODULE)-decl.txt $(DOC_MODULE)-decl-list.txt $(DOC_MODULE)-decl.txt
@if test "$(abs_srcdir)" != "$(abs_builddir)" ; then \ @if test "$(abs_srcdir)" != "$(abs_builddir)" ; then \
rm -f $(SETUP_FILES) $(DOC_MODULE).types; \ rm -f $(SETUP_FILES) $(expand_content_files) $(DOC_MODULE).types; \
fi fi
maintainer-clean-local: maintainer-clean-local:

View File

@ -41,6 +41,8 @@ extern "C" {
int vips_shrink( VipsImage *in, VipsImage **out, int vips_shrink( VipsImage *in, VipsImage **out,
double xshrink, double yshrink, ... ) double xshrink, double yshrink, ... )
__attribute__((sentinel)); __attribute__((sentinel));
int vips_shrinkh( VipsImage *in, VipsImage **out, int xshrink, ... );
int vips_shrinkv( VipsImage *in, VipsImage **out, int yshrink, ... );
int vips_similarity( VipsImage *in, VipsImage **out, ... ) int vips_similarity( VipsImage *in, VipsImage **out, ... )
__attribute__((sentinel)); __attribute__((sentinel));
int vips_resize( VipsImage *in, VipsImage **out, double scale, ... ) int vips_resize( VipsImage *in, VipsImage **out, double scale, ... )

View File

@ -6,6 +6,8 @@ libresample_la_SOURCES = \
resize.c \ resize.c \
presample.h \ presample.h \
shrink.c \ shrink.c \
shrinkh.c \
shrinkv.c \
interpolate.c \ interpolate.c \
transform.c \ transform.c \
bicubic.cpp \ bicubic.cpp \

View File

@ -112,12 +112,16 @@ void
vips_resample_operation_init( void ) vips_resample_operation_init( void )
{ {
extern GType vips_shrink_get_type( void ); extern GType vips_shrink_get_type( void );
extern GType vips_shrinkh_get_type( void );
extern GType vips_shrinkv_get_type( void );
extern GType vips_quadratic_get_type( void ); extern GType vips_quadratic_get_type( void );
extern GType vips_affine_get_type( void ); extern GType vips_affine_get_type( void );
extern GType vips_similarity_get_type( void ); extern GType vips_similarity_get_type( void );
extern GType vips_resize_get_type( void ); extern GType vips_resize_get_type( void );
vips_shrink_get_type(); vips_shrink_get_type();
vips_shrinkh_get_type();
vips_shrinkv_get_type();
vips_quadratic_get_type(); vips_quadratic_get_type();
vips_affine_get_type(); vips_affine_get_type();
vips_similarity_get_type(); vips_similarity_get_type();

162
libvips/resample/shrink2.c Normal file
View File

@ -0,0 +1,162 @@
/* shrink with a box filter
*
* 30/10/15
* - from shrink.c
*/
/*
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 DEBUG
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <vips/intl.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <vips/vips.h>
#include <vips/debug.h>
#include <vips/internal.h>
#include "presample.h"
typedef struct _VipsShrink2 {
VipsResample parent_instance;
int xshrink; /* Shrink factors */
int yshrink;
} VipsShrink2;
typedef VipsResampleClass VipsShrink2Class;
G_DEFINE_TYPE( VipsShrink2, vips_shrink2, VIPS_TYPE_RESAMPLE );
static int
vips_shrink2_build( VipsObject *object )
{
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );
VipsResample *resample = VIPS_RESAMPLE( object );
VipsShrink2 *shrink = (VipsShrink2 *) object;
VipsImage **t = (VipsImage **)
vips_object_local_array( object, 1 );
VipsImage *in;
if( VIPS_OBJECT_CLASS( vips_shrink2_parent_class )->build( object ) )
return( -1 );
in = resample->in;
if( vips_shrinkh( in, &t[0], shrink->xshrink, NULL ) ||
vips_shrinkv( t[0], &t[1], shrink->yshrink, NULL ) )
return( -1 );
in = t[1];
return( 0 );
}
static void
vips_shrink2_class_init( VipsShrink2Class *class )
{
GObjectClass *gobject_class = G_OBJECT_CLASS( class );
VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( class );
VipsOperationClass *operation_class = VIPS_OPERATION_CLASS( class );
VIPS_DEBUG_MSG( "vips_shrink2_class_init\n" );
gobject_class->set_property = vips_object_set_property;
gobject_class->get_property = vips_object_get_property;
vobject_class->nickname = "shrink2";
vobject_class->description = _( "shrink an image" );
vobject_class->build = vips_shrink2_build;
operation_class->flags = VIPS_OPERATION_SEQUENTIAL;
VIPS_ARG_INT( class, "xshrink", 8,
_( "Xshrink" ),
_( "Horizontal shrink factor" ),
VIPS_ARGUMENT_REQUIRED_INPUT,
G_STRUCT_OFFSET( VipsShrink2, xshrink ),
1.0, 1000000, 1 );
VIPS_ARG_INT( class, "yshrink", 9,
_( "Yshrink" ),
_( "Vertical shrink factor" ),
VIPS_ARGUMENT_REQUIRED_INPUT,
G_STRUCT_OFFSET( VipsShrink2, yshrink ),
1.0, 1000000, 1 );
}
static void
vips_shrink2_init( VipsShrink2 *shrink )
{
}
/**
* vips_shrink2:
* @in: input image
* @out: output image
* @xshrink: horizontal shrink
* @yshrink: vertical shrink
* @...: %NULL-terminated list of optional named arguments
*
* Shrink @in by a pair of factors with a simple box filter.
*
* You will get aliasing for non-integer shrinks. In this case, shrink with
* this function to the nearest integer size above the target shrink, then
* downsample to the exact size with vips_affine() and your choice of
* interpolator. See vips_resize() for a convenient way to do this.
*
* This operation does not change xres or yres. The image resolution needs to
* be updated by the application.
*
* See also: vips_resize(), vips_affine().
*
* Returns: 0 on success, -1 on error
*/
int
vips_shrink2( VipsImage *in, VipsImage **out,
int xshrink, int yshrink, ... )
{
va_list ap;
int result;
va_start( ap, yshrink );
result = vips_call_split( "shrink2", ap, in, out, xshrink, yshrink );
va_end( ap );
return( result );
}

374
libvips/resample/shrinkh.c Normal file
View File

@ -0,0 +1,374 @@
/* horizontal shrink by an integer factor
*
* 30/10/15
* - from shrink.c
*/
/*
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 DEBUG
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <vips/intl.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <vips/vips.h>
#include <vips/debug.h>
#include <vips/internal.h>
#include "presample.h"
typedef struct _VipsShrinkh {
VipsResample parent_instance;
int xshrink; /* Shrink factor */
} VipsShrinkh;
typedef VipsResampleClass VipsShrinkhClass;
G_DEFINE_TYPE( VipsShrinkh, vips_shrinkh, VIPS_TYPE_RESAMPLE );
/* Our per-sequence parameter struct. Somewhere to sum band elements.
*/
typedef struct {
VipsRegion *ir;
VipsPel *sum;
} VipsShrinkhSequence;
/* Free a sequence value.
*/
static int
vips_shrinkh_stop( void *vseq, void *a, void *b )
{
VipsShrinkhSequence *seq = (VipsShrinkhSequence *) vseq;
VIPS_FREEF( g_object_unref, seq->ir );
return( 0 );
}
/* Make a sequence value.
*/
static void *
vips_shrinkh_start( VipsImage *out, void *a, void *b )
{
VipsImage *in = (VipsImage *) a;
VipsShrinkhSequence *seq;
if( !(seq = VIPS_NEW( out, VipsShrinkhSequence )) )
return( NULL );
seq->ir = vips_region_new( in );
/* Big enough for the largest intermediate.
*/
seq->sum = VIPS_ARRAY( out,
in->Bands * vips_format_sizeof( VIPS_FORMAT_DPCOMPLEX ),
VipsPel );
return( (void *) seq );
}
/* Integer shrink.
*/
#define ISHRINK( TYPE ) { \
int *sum = (int *) seq->sum; \
TYPE *p = (TYPE *) in; \
TYPE *q = (TYPE *) out; \
\
for( x = 0; x < width; x++ ) { \
for( b = 0; b < bands; b++ ) \
sum[b] = 0; \
\
for( x1 = 0; x1 < shrink->xshrink; x1++ ) \
for( b = 0; b < bands; b++ ) \
sum[b] += *p++; \
\
for( b = 0; b < bands; b++ ) \
*q++ = (sum[b] + shrink->xshrink / 2) / \
shrink->xshrink; \
} \
}
/* Float shrink.
*/
#define FSHRINK( TYPE ) { \
double *sum = (double *) seq->sum; \
TYPE *p = (TYPE *) in; \
TYPE *q = (TYPE *) out; \
\
for( x = 0; x < width; x++ ) { \
for( b = 0; b < bands; b++ ) \
sum[b] = 0.0; \
\
for( x1 = 0; x1 < shrink->xshrink; x1++ ) \
for( b = 0; b < bands; b++ ) \
sum[b] += *p++; \
\
for( b = 0; b < bands; b++ ) \
*q++ = sum[b] / shrink->xshrink; \
} \
}
/* Generate an area of @or. @ir is large enough.
*/
static void
vips_shrinkh_gen2( VipsShrinkh *shrink, VipsShrinkhSequence *seq,
VipsRegion *or, VipsRegion *ir,
int left, int top, int width )
{
VipsResample *resample = VIPS_RESAMPLE( shrink );
const int bands = resample->in->Bands *
(vips_band_format_iscomplex( resample->in->BandFmt ) ?
2 : 1);
VipsPel *out = VIPS_REGION_ADDR( or, left, top );
VipsPel *in = VIPS_REGION_ADDR( ir, left * shrink->xshrink, top );
int x;
int x1, b;
switch( resample->in->BandFmt ) {
case VIPS_FORMAT_UCHAR:
ISHRINK( unsigned char ); break;
case VIPS_FORMAT_CHAR:
ISHRINK( char ); break;
case VIPS_FORMAT_USHORT:
ISHRINK( unsigned short ); break;
case VIPS_FORMAT_SHORT:
ISHRINK( short ); break;
case VIPS_FORMAT_UINT:
ISHRINK( unsigned int ); break;
case VIPS_FORMAT_INT:
ISHRINK( int ); break;
case VIPS_FORMAT_FLOAT:
FSHRINK( float ); break;
case VIPS_FORMAT_DOUBLE:
FSHRINK( double ); break;
case VIPS_FORMAT_COMPLEX:
FSHRINK( float ); break;
case VIPS_FORMAT_DPCOMPLEX:
FSHRINK( double ); break;
default:
g_assert( 0 );
}
}
static int
vips_shrinkh_gen( VipsRegion *or, void *vseq,
void *a, void *b, gboolean *stop )
{
VipsShrinkhSequence *seq = (VipsShrinkhSequence *) vseq;
VipsShrinkh *shrink = (VipsShrinkh *) b;
VipsRegion *ir = seq->ir;
VipsRect *r = &or->valid;
int y;
/* How do we chunk up the image? We don't want to prepare the whole of
* the input region corresponding to *r since it could be huge.
*
* Request input a line at a time.
*
* We don't chunk horizontally. We want "vips shrink x.jpg b.jpg 100
* 100" to run sequentially. If we chunk horizontally, we will fetch
* 100x100 lines from the top of the image, then 100x100 100 lines
* down, etc. for each thread, then when they've finished, fetch
* 100x100, 100 pixels across from the top of the image. This will
* break sequentiality.
*/
#ifdef DEBUG
printf( "vips_shrinkh_gen: generating %d x %d at %d x %d\n",
r->width, r->height, r->left, r->top );
#endif /*DEBUG*/
for( y = 0; y < r->height; y ++ ) {
VipsRect s;
s.left = r->left * shrink->xshrink;
s.top = r->top + y;
s.width = ceil( r->width * shrink->xshrink );
s.height = 1;
#ifdef DEBUG
printf( "shrinkh_gen: requesting line %d\n", s.top );
#endif /*DEBUG*/
if( vips_region_prepare( ir, &s ) )
return( -1 );
VIPS_GATE_START( "vips_shrinkh_gen: work" );
vips_shrinkh_gen2( shrink, seq,
or, ir,
r->left, r->top + y, r->width );
VIPS_GATE_STOP( "vips_shrinkh_gen: work" );
}
return( 0 );
}
static int
vips_shrinkh_build( VipsObject *object )
{
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );
VipsResample *resample = VIPS_RESAMPLE( object );
VipsShrinkh *shrink = (VipsShrinkh *) object;
VipsImage **t = (VipsImage **)
vips_object_local_array( object, 1 );
VipsImage *in;
if( VIPS_OBJECT_CLASS( vips_shrinkh_parent_class )->build( object ) )
return( -1 );
in = resample->in;
if( shrink->xshrink < 1 ) {
vips_error( class->nickname,
"%s", _( "shrink factors should be >= 1" ) );
return( -1 );
}
if( shrink->xshrink == 1 )
return( vips_image_write( in, resample->out ) );
/* Unpack for processing.
*/
if( vips_image_decode( in, &t[0] ) )
return( -1 );
in = t[0];
/* THINSTRIP will work, anything else will break seq mode. If you
* combine shrink with conv you'll need to use a line cache to maintain
* sequentiality.
*/
if( vips_image_pipelinev( resample->out,
VIPS_DEMAND_STYLE_THINSTRIP, in, NULL ) )
return( -1 );
/* Size output. Note: we round the output width down!
*
* Don't change xres/yres, leave that to the application layer. For
* example, vipsthumbnail knows the true shrink factor (including the
* fractional part), we just see the integer part here.
*/
resample->out->Xsize = in->Xsize / shrink->xshrink;
if( resample->out->Xsize <= 0 ) {
vips_error( class->nickname,
"%s", _( "image has shrunk to nothing" ) );
return( -1 );
}
#ifdef DEBUG
printf( "vips_shrinkh_build: shrinking %d x %d image to %d x %d\n",
in->Xsize, in->Ysize,
resample->out->Xsize, resample->out->Ysize );
#endif /*DEBUG*/
if( vips_image_generate( resample->out,
vips_shrinkh_start, vips_shrinkh_gen, vips_shrinkh_stop,
in, shrink ) )
return( -1 );
return( 0 );
}
static void
vips_shrinkh_class_init( VipsShrinkhClass *class )
{
GObjectClass *gobject_class = G_OBJECT_CLASS( class );
VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( class );
VipsOperationClass *operation_class = VIPS_OPERATION_CLASS( class );
VIPS_DEBUG_MSG( "vips_shrinkh_class_init\n" );
gobject_class->set_property = vips_object_set_property;
gobject_class->get_property = vips_object_get_property;
vobject_class->nickname = "shrinkh";
vobject_class->description = _( "shrink an image horizontally" );
vobject_class->build = vips_shrinkh_build;
operation_class->flags = VIPS_OPERATION_SEQUENTIAL;
VIPS_ARG_INT( class, "xshrink", 8,
_( "Xshrink" ),
_( "Horizontal shrink factor" ),
VIPS_ARGUMENT_REQUIRED_INPUT,
G_STRUCT_OFFSET( VipsShrinkh, xshrink ),
1, 1000000, 1 );
}
static void
vips_shrinkh_init( VipsShrinkh *shrink )
{
}
/**
* vips_shrinkh:
* @in: input image
* @out: output image
* @xshrink: horizontal shrink
* @...: %NULL-terminated list of optional named arguments
*
* Shrink @in horizontally by an integer factor. Each pixel in the output is
* the average of the corresponding line of @xshrink pixels in the input.
*
* This is a evry low-level operation: see vips_resize() for a more
* convenient way to resize images.
*
* This operation does not change xres or yres. The image resolution needs to
* be updated by the application.
*
* See also: vips_shrinkv(), vips_shrink(), vips_resize(), vips_affine().
*
* Returns: 0 on success, -1 on error
*/
int
vips_shrinkh( VipsImage *in, VipsImage **out, int xshrink, ... )
{
va_list ap;
int result;
va_start( ap, xshrink );
result = vips_call_split( "shrinkh", ap, in, out, xshrink );
va_end( ap );
return( result );
}

459
libvips/resample/shrinkv.c Normal file
View File

@ -0,0 +1,459 @@
/* shrink with a box filter
*
* Copyright: 1990, N. Dessipris.
*
* Authors: Nicos Dessipris and Kirk Martinez
* Written on: 29/04/1991
* Modified on: 2/11/92, 22/2/93 Kirk Martinez - Xres Yres & cleanup
incredibly inefficient for box filters as LUTs are used instead of +
Needs converting to a smoother filter: eg Gaussian! KM
* 15/7/93 JC
* - rewritten for partial v2
* - ANSIfied
* - now shrinks any non-complex type
* - no longer cloned from im_convsub()
* - could be much better! see km comments above
* 3/8/93 JC
* - rounding bug fixed
* 11/1/94 JC
* - problems with .000001 and round up/down ignored! Try shrink 3738
* pixel image by 9.345000000001
* 7/10/94 JC
* - IM_NEW and IM_ARRAY added
* - more typedef
* 3/7/95 JC
* - IM_CODING_LABQ handling added here
* 20/12/08
* - fall back to im_copy() for 1/1 shrink
* 2/2/11
* - gtk-doc
* 10/2/12
* - shrink in chunks to reduce peak memuse for large shrinks
* - simpler
* 12/6/12
* - redone as a class
* - warn about non-int shrinks
* - some tuning .. tried an int coordinate path, not worthwhile
* 16/11/12
* - don't change xres/yres, see comment below
* 8/4/13
* - oops demand_hint was incorrect, thanks Jan
* 6/6/13
* - don't chunk horizontally, fixes seq problems with large shrink
* factors
*/
/*
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 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/debug.h>
#include <vips/internal.h>
#include "presample.h"
typedef struct _VipsShrinkv {
VipsResample parent_instance;
int yshrink;
size_t sizeof_line_buffer;
} VipsShrinkv;
typedef VipsResampleClass VipsShrinkvClass;
G_DEFINE_TYPE( VipsShrinkv, vips_shrinkv, VIPS_TYPE_RESAMPLE );
/* Our per-sequence parameter struct. Somewhere to sum band elements.
*/
typedef struct {
VipsRegion *ir;
VipsPel *sum;
} VipsShrinkvSequence;
/* Free a sequence value.
*/
static int
vips_shrinkv_stop( void *vseq, void *a, void *b )
{
VipsShrinkvSequence *seq = (VipsShrinkvSequence *) vseq;
VIPS_FREEF( g_object_unref, seq->ir );
return( 0 );
}
/* Make a sequence value.
*/
static void *
vips_shrinkv_start( VipsImage *out, void *a, void *b )
{
VipsImage *in = (VipsImage *) a;
VipsShrinkv *shrink = (VipsShrinkv *) b;
VipsShrinkvSequence *seq;
if( !(seq = VIPS_NEW( out, VipsShrinkvSequence )) )
return( NULL );
seq->ir = vips_region_new( in );
/* Big enough for the largest intermediate .. a whole scanline.
*/
seq->sum = VIPS_ARRAY( out, shrink->sizeof_line_buffer, VipsPel );
return( (void *) seq );
}
#define ADD( ACC_TYPE, TYPE ) { \
ACC_TYPE *sum = (ACC_TYPE *) seq->sum; \
TYPE *p = (TYPE *) in; \
\
for( x = 0; x < sz; x++ ) \
sum[x] += p[x]; \
}
/* Add a line of pixels to sum.
*/
static void
vips_shrinkv_add_line( VipsShrinkv *shrink, VipsShrinkvSequence *seq,
VipsRegion *ir, int left, int top, int width )
{
VipsResample *resample = VIPS_RESAMPLE( shrink );
const int bands = resample->in->Bands *
(vips_band_format_iscomplex( resample->in->BandFmt ) ?
2 : 1);
const int sz = bands * width;
int x;
VipsPel *in = VIPS_REGION_ADDR( ir, left, top );
switch( resample->in->BandFmt ) {
case VIPS_FORMAT_UCHAR:
ADD( int, unsigned char ); break;
case VIPS_FORMAT_CHAR:
ADD( int, char ); break;
case VIPS_FORMAT_USHORT:
ADD( int, unsigned short ); break;
case VIPS_FORMAT_SHORT:
ADD( int, short ); break;
case VIPS_FORMAT_UINT:
ADD( int, unsigned int ); break;
case VIPS_FORMAT_INT:
ADD( int, int ); break;
case VIPS_FORMAT_FLOAT:
ADD( double, float ); break;
case VIPS_FORMAT_DOUBLE:
ADD( double, double ); break;
case VIPS_FORMAT_COMPLEX:
ADD( double, float ); break;
case VIPS_FORMAT_DPCOMPLEX:
ADD( double, double ); break;
default:
g_assert( 0 );
}
}
/* Integer average.
*/
#define IAVG( TYPE ) { \
int *sum = (int *) seq->sum; \
TYPE *q = (TYPE *) out; \
\
for( x = 0; x < sz; x++ ) \
q[x] = (sum[x] + shrink->yshrink / 2) / shrink->yshrink; \
}
/* Float average.
*/
#define FAVG( TYPE ) { \
double *sum = (double *) seq->sum; \
TYPE *q = (TYPE *) out; \
\
for( x = 0; x < sz; x++ ) \
q[x] = sum[x] / shrink->yshrink; \
}
/* Average the line of sums to out.
*/
static void
vips_shrinkv_write_line( VipsShrinkv *shrink, VipsShrinkvSequence *seq,
VipsRegion *or, int left, int top, int width )
{
VipsResample *resample = VIPS_RESAMPLE( shrink );
const int bands = resample->in->Bands *
(vips_band_format_iscomplex( resample->in->BandFmt ) ?
2 : 1);
const int sz = bands * width;
int x;
VipsPel *out = VIPS_REGION_ADDR( or, left, top );
switch( resample->in->BandFmt ) {
case VIPS_FORMAT_UCHAR:
IAVG( unsigned char ); break;
case VIPS_FORMAT_CHAR:
IAVG( char ); break;
case VIPS_FORMAT_USHORT:
IAVG( unsigned short ); break;
case VIPS_FORMAT_SHORT:
IAVG( short ); break;
case VIPS_FORMAT_UINT:
IAVG( unsigned int ); break;
case VIPS_FORMAT_INT:
IAVG( int ); break;
case VIPS_FORMAT_FLOAT:
FAVG( float ); break;
case VIPS_FORMAT_DOUBLE:
FAVG( double ); break;
case VIPS_FORMAT_COMPLEX:
FAVG( float ); break;
case VIPS_FORMAT_DPCOMPLEX:
FAVG( double ); break;
default:
g_assert( 0 );
}
}
static int
vips_shrinkv_gen( VipsRegion *or, void *vseq,
void *a, void *b, gboolean *stop )
{
VipsShrinkvSequence *seq = (VipsShrinkvSequence *) vseq;
VipsShrinkv *shrink = (VipsShrinkv *) b;
VipsRegion *ir = seq->ir;
VipsRect *r = &or->valid;
int y, y1;
/* How do we chunk up the image? We don't want to prepare the whole of
* the input region corresponding to *r since it could be huge.
*
* Request input a line at a time, average to a line buffer.
*
* We don't chunk horizontally. We want "vips shrink x.jpg b.jpg 100
* 100" to run sequentially. If we chunk horizontally, we will fetch
* 100x100 lines from the top of the image, then 100x100 100 lines
* down, etc. for each thread, then when they've finished, fetch
* 100x100, 100 pixels across from the top of the image. This will
* break sequentiality.
*/
#ifdef DEBUG
printf( "vips_shrinkv_gen: generating %d x %d at %d x %d\n",
r->width, r->height, r->left, r->top );
#endif /*DEBUG*/
for( y = 0; y < r->height; y++ ) {
memset( seq->sum, 0, shrink->sizeof_line_buffer );
for( y1 = 0; y1 < shrink->yshrink; y1++ ) {
VipsRect s;
s.left = r->left;
s.top = y1 + (y + r->top) * shrink->yshrink;
s.width = r->width;
s.height = 1;
#ifdef DEBUG
printf( "shrink_gen: requesting line %d\n", s.top );
#endif /*DEBUG*/
if( vips_region_prepare( ir, &s ) )
return( -1 );
VIPS_GATE_START( "vips_shrinkv_gen: work" );
vips_shrinkv_add_line( shrink, seq, ir,
s.left, s.top, s.width );
VIPS_GATE_STOP( "vips_shrinkv_gen: work" );
}
VIPS_GATE_START( "vips_shrinkv_gen: work" );
vips_shrinkv_write_line( shrink, seq, or,
r->left, r->top + y, r->width );
VIPS_GATE_STOP( "vips_shrinkv_gen: work" );
}
return( 0 );
}
static int
vips_shrinkv_build( VipsObject *object )
{
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );
VipsResample *resample = VIPS_RESAMPLE( object );
VipsShrinkv *shrink = (VipsShrinkv *) object;
VipsImage **t = (VipsImage **)
vips_object_local_array( object, 1 );
VipsImage *in;
if( VIPS_OBJECT_CLASS( vips_shrinkv_parent_class )->build( object ) )
return( -1 );
in = resample->in;
if( shrink->yshrink < 1 ) {
vips_error( class->nickname,
"%s", _( "shrink factors should be >= 1" ) );
return( -1 );
}
if( shrink->yshrink == 1 )
return( vips_image_write( in, resample->out ) );
/* Unpack for processing.
*/
if( vips_image_decode( in, &t[0] ) )
return( -1 );
in = t[0];
/* We have to keep a line buffer as we sum columns.
*/
shrink->sizeof_line_buffer =
in->Xsize * in->Bands *
vips_format_sizeof( VIPS_FORMAT_DPCOMPLEX );
/* THINSTRIP will work, anything else will break seq mode. If you
* combine shrink with conv you'll need to use a line cache to maintain
* sequentiality.
*/
if( vips_image_pipelinev( resample->out,
VIPS_DEMAND_STYLE_THINSTRIP, in, NULL ) )
return( -1 );
/* Size output. Note: we round the output width down!
*
* Don't change xres/yres, leave that to the application layer. For
* example, vipsthumbnail knows the true shrink factor (including the
* fractional part), we just see the integer part here.
*/
resample->out->Ysize = in->Ysize / shrink->yshrink;
if( resample->out->Ysize <= 0 ) {
vips_error( class->nickname,
"%s", _( "image has shrunk to nothing" ) );
return( -1 );
}
#ifdef DEBUG
printf( "vips_shrinkv_build: shrinking %d x %d image to %d x %d\n",
in->Xsize, in->Ysize,
resample->out->Xsize, resample->out->Ysize );
#endif /*DEBUG*/
if( vips_image_generate( resample->out,
vips_shrinkv_start, vips_shrinkv_gen, vips_shrinkv_stop,
in, shrink ) )
return( -1 );
return( 0 );
}
static void
vips_shrinkv_class_init( VipsShrinkvClass *class )
{
GObjectClass *gobject_class = G_OBJECT_CLASS( class );
VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( class );
VipsOperationClass *operation_class = VIPS_OPERATION_CLASS( class );
VIPS_DEBUG_MSG( "vips_shrinkv_class_init\n" );
gobject_class->set_property = vips_object_set_property;
gobject_class->get_property = vips_object_get_property;
vobject_class->nickname = "shrinkv";
vobject_class->description = _( "shrink an image vertically" );
vobject_class->build = vips_shrinkv_build;
operation_class->flags = VIPS_OPERATION_SEQUENTIAL;
VIPS_ARG_INT( class, "yshrink", 9,
_( "Yshrink" ),
_( "Vertical shrink factor" ),
VIPS_ARGUMENT_REQUIRED_INPUT,
G_STRUCT_OFFSET( VipsShrinkv, yshrink ),
1, 1000000, 1 );
}
static void
vips_shrinkv_init( VipsShrinkv *shrink )
{
}
/**
* vips_shrinkv:
* @in: input image
* @out: output image
* @yshrink: vertical shrink
* @...: %NULL-terminated list of optional named arguments
*
* Shrink @in vertically by an integer factor. Each pixel in the output is
* the average of the corresponding column of @yshrink pixels in the input.
*
* You will get aliasing for non-integer shrinks. In this case, shrink with
* this function to the nearest integer size above the target shrink, then
* downsample to the exact size with vips_affine() and your choice of
* interpolator. See vips_resize() for a convenient way to do this.
*
* This operation does not change xres or yres. The image resolution needs to
* be updated by the application.
*
* See also: vips_shrinkh(), vips_shrink(), vips_resize(), vips_affine().
*
* Returns: 0 on success, -1 on error
*/
int
vips_shrinkv( VipsImage *in, VipsImage **out, int yshrink, ... )
{
va_list ap;
int result;
va_start( ap, yshrink );
result = vips_call_split( "shrinkv", ap, in, out, yshrink );
va_end( ap );
return( result );
}