From da8f236f95757d0431049b5e6eb8e76550526707 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 30 Oct 2015 18:15:14 +0000 Subject: [PATCH] try a split shrink not quite done yet --- TODO | 13 + doc/gtk-doc.make | 20 +- libvips/include/vips/resample.h | 2 + libvips/resample/Makefile.am | 2 + libvips/resample/resample.c | 4 + libvips/resample/shrink2.c | 162 +++++++++++ libvips/resample/shrinkh.c | 374 ++++++++++++++++++++++++++ libvips/resample/shrinkv.c | 459 ++++++++++++++++++++++++++++++++ 8 files changed, 1024 insertions(+), 12 deletions(-) create mode 100644 libvips/resample/shrink2.c create mode 100644 libvips/resample/shrinkh.c create mode 100644 libvips/resample/shrinkv.c diff --git a/TODO b/TODO index 16156fa7..9b516bd1 100644 --- a/TODO +++ b/TODO @@ -14,6 +14,19 @@ 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 + 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 see https://github.com/jcupitt/libvips/issues/325 diff --git a/doc/gtk-doc.make b/doc/gtk-doc.make index 9ccd0b04..e7916563 100644 --- a/doc/gtk-doc.make +++ b/doc/gtk-doc.make @@ -25,7 +25,6 @@ TARGET_DIR=$(HTML_DIR)/$(DOC_MODULE) SETUP_FILES = \ $(content_files) \ - $(expand_content_files) \ $(DOC_MAIN_SGML_FILE) \ $(DOC_MODULE)-sections.txt \ $(DOC_MODULE)-overrides.txt @@ -87,7 +86,7 @@ GTK_DOC_V_SETUP_0=@echo " DOC Preparing build"; setup-build.stamp: -$(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 \ for file in $$files ; do \ 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 \ scanobj_options=""; \ gtkdoc-scangobj 2>&1 --help | grep >/dev/null "\-\-verbose"; \ - if test "$$?" = "0"; then \ + if test "$(?)" = "0"; then \ if test "x$(V)" = "x1"; then \ scanobj_options="--verbose"; \ 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_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 && \ mkhtml_options=""; \ gtkdoc-mkhtml 2>&1 --help | grep >/dev/null "\-\-verbose"; \ - if test "$$?" = "0"; then \ + if test "$(?)" = "0"; then \ if test "x$(V)" = "x1"; then \ mkhtml_options="$$mkhtml_options --verbose"; \ fi; \ fi; \ gtkdoc-mkhtml 2>&1 --help | grep >/dev/null "\-\-path"; \ - if test "$$?" = "0"; then \ + if test "$(?)" = "0"; then \ mkhtml_options="$$mkhtml_options --path=\"$(abs_srcdir)\""; \ fi; \ 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_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 && \ mkpdf_options=""; \ gtkdoc-mkpdf 2>&1 --help | grep >/dev/null "\-\-verbose"; \ - if test "$$?" = "0"; then \ + if test "$(?)" = "0"; then \ if test "x$(V)" = "x1"; then \ mkpdf_options="$$mkpdf_options --verbose"; \ fi; \ @@ -224,15 +223,12 @@ clean-local: @if echo $(SCAN_OPTIONS) | grep -q "\-\-rebuild-types" ; then \ rm -f $(DOC_MODULE).types; \ fi - @if echo $(SCAN_OPTIONS) | grep -q "\-\-rebuild-sections" ; then \ - rm -f $(DOC_MODULE)-sections.txt; \ - fi distclean-local: @rm -rf xml html $(REPORT_FILES) $(DOC_MODULE).pdf \ $(DOC_MODULE)-decl-list.txt $(DOC_MODULE)-decl.txt @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 maintainer-clean-local: diff --git a/libvips/include/vips/resample.h b/libvips/include/vips/resample.h index f8b92ee2..7807e08d 100644 --- a/libvips/include/vips/resample.h +++ b/libvips/include/vips/resample.h @@ -41,6 +41,8 @@ extern "C" { int vips_shrink( VipsImage *in, VipsImage **out, double xshrink, double yshrink, ... ) __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, ... ) __attribute__((sentinel)); int vips_resize( VipsImage *in, VipsImage **out, double scale, ... ) diff --git a/libvips/resample/Makefile.am b/libvips/resample/Makefile.am index afc0896c..bb9f8db2 100644 --- a/libvips/resample/Makefile.am +++ b/libvips/resample/Makefile.am @@ -6,6 +6,8 @@ libresample_la_SOURCES = \ resize.c \ presample.h \ shrink.c \ + shrinkh.c \ + shrinkv.c \ interpolate.c \ transform.c \ bicubic.cpp \ diff --git a/libvips/resample/resample.c b/libvips/resample/resample.c index 9c59bdcc..83d8fe7b 100644 --- a/libvips/resample/resample.c +++ b/libvips/resample/resample.c @@ -112,12 +112,16 @@ void vips_resample_operation_init( 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_affine_get_type( void ); extern GType vips_similarity_get_type( void ); extern GType vips_resize_get_type( void ); vips_shrink_get_type(); + vips_shrinkh_get_type(); + vips_shrinkv_get_type(); vips_quadratic_get_type(); vips_affine_get_type(); vips_similarity_get_type(); diff --git a/libvips/resample/shrink2.c b/libvips/resample/shrink2.c new file mode 100644 index 00000000..dc0dc59d --- /dev/null +++ b/libvips/resample/shrink2.c @@ -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 +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#include + +#include +#include +#include + +#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 ); +} diff --git a/libvips/resample/shrinkh.c b/libvips/resample/shrinkh.c new file mode 100644 index 00000000..149cc7ef --- /dev/null +++ b/libvips/resample/shrinkh.c @@ -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 +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#include + +#include +#include +#include + +#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 ); +} diff --git a/libvips/resample/shrinkv.c b/libvips/resample/shrinkv.c new file mode 100644 index 00000000..820c430d --- /dev/null +++ b/libvips/resample/shrinkv.c @@ -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 +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#include +#include + +#include +#include +#include + +#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 ); +}