diff --git a/libvips/foreign/dzsave.c b/libvips/foreign/dzsave.c index 471ee13d..737d2b69 100644 --- a/libvips/foreign/dzsave.c +++ b/libvips/foreign/dzsave.c @@ -447,6 +447,7 @@ struct _VipsForeignSaveDz { VipsAngle angle; VipsForeignDzContainer container; int compression; + VipsRegionShrink region_shrink; /* Tile and overlap geometry. The members above are the parameters we * accept, this next set are the derived values which are actually @@ -1355,6 +1356,8 @@ strip_shrink( Layer *layer ) Layer *below = layer->below; VipsRegion *from = layer->strip; VipsRegion *to = below->strip; + VipsForeignSaveDz *dz = layer->dz; + VipsRegionShrink region_shrink = dz->region_shrink; VipsRect target; VipsRect source; @@ -1401,10 +1404,11 @@ strip_shrink( Layer *layer ) /* None? All done. */ - if( vips_rect_isempty( &target ) ) + if( vips_rect_isempty( &target ) ) break; - (void) vips_region_shrink( from, to, &target ); + (void) vips_region_shrink( from, to, &target, + region_shrink ); below->write_y += target.height; @@ -2098,6 +2102,14 @@ vips_foreign_save_dz_class_init( VipsForeignSaveDzClass *class ) G_STRUCT_OFFSET( VipsForeignSaveDz, compression ), -1, 9, 0 ); + VIPS_ARG_ENUM( class, "region_shrink", 18, + _( "Region shrink" ), + _( "Method for blah" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveDz, region_shrink ), + VIPS_TYPE_REGION_SHRINK, + VIPS_REGION_SHRINK_MEAN ); + /* How annoying. We stupidly had these in earlier versions. */ @@ -2136,6 +2148,7 @@ vips_foreign_save_dz_init( VipsForeignSaveDz *dz ) dz->angle = VIPS_ANGLE_D0; dz->container = VIPS_FOREIGN_DZ_CONTAINER_FS; dz->compression = 0; + dz->region_shrink = VIPS_REGION_SHRINK_MEAN; } typedef struct _VipsForeignSaveDzFile { @@ -2332,6 +2345,7 @@ vips_foreign_save_dz_buffer_init( VipsForeignSaveDzBuffer *buffer ) * * @container: #VipsForeignDzContainer set container type * * @properties: %gboolean write a properties file * * @compression: %gint zip deflate compression level + * * @shrink_region: #VipsRegionShrink How to shrink each 2x2 region. * * Save an image as a set of tiles at various resolutions. By default dzsave * uses DeepZoom layout -- use @layout to pick other conventions. @@ -2375,6 +2389,10 @@ vips_foreign_save_dz_buffer_init( VipsForeignSaveDzBuffer *buffer ) * (use zlib default), 0 (store, compression disabled) to 9 (max compression). * If no value is given, the default is to store files without compression. * + * You can use @region_shrink to control the method for shrinking each 2x2 + * region. This defaults to using the average of the 4 input pixels but you can + * also use the median in cases where you want to preseve the range of values. + * * See also: vips_tiffsave(). * * Returns: 0 on success, -1 on error. diff --git a/libvips/foreign/vips2tiff.c b/libvips/foreign/vips2tiff.c index 1350befe..d1220821 100644 --- a/libvips/foreign/vips2tiff.c +++ b/libvips/foreign/vips2tiff.c @@ -1389,10 +1389,11 @@ layer_strip_shrink( Layer *layer ) /* None? All done. */ - if( vips_rect_isempty( &target ) ) + if( vips_rect_isempty( &target ) ) break; - (void) vips_region_shrink( from, to, &target ); + (void) vips_region_shrink( from, to, &target, + VIPS_REGION_SHRINK_MEAN ); below->write_y += target.height; diff --git a/libvips/include/vips/Makefile.am b/libvips/include/vips/Makefile.am index 636b33a3..6d296905 100644 --- a/libvips/include/vips/Makefile.am +++ b/libvips/include/vips/Makefile.am @@ -72,7 +72,8 @@ vips_scan_headers = \ ${top_srcdir}/libvips/include/vips/morphology.h \ ${top_srcdir}/libvips/include/vips/draw.h \ ${top_srcdir}/libvips/include/vips/basic.h \ - ${top_srcdir}/libvips/include/vips/object.h + ${top_srcdir}/libvips/include/vips/object.h \ + ${top_srcdir}/libvips/include/vips/region.h enumtypes.h: $(vips_scan_headers) Makefile.am glib-mkenums --template enumtemplate $(vips_scan_headers) > enumtypes.h diff --git a/libvips/include/vips/region.h b/libvips/include/vips/region.h index 3d5972c4..bf0c877d 100644 --- a/libvips/include/vips/region.h +++ b/libvips/include/vips/region.h @@ -55,6 +55,21 @@ extern "C" { (G_TYPE_INSTANCE_GET_CLASS( (obj), \ VIPS_TYPE_REGION, VipsRegionClass )) +/** + * VipsRegionShrink: + * @VIPS_REGION_SHRINK_MEAN: use the average + * @VIPS_REGION_SHRINK_MEDIAN: use the median + * @VIPS_REGION_SHRINK_MODE: use the mode + * + * How to calculate the output pixels when shrinking a 2x2 region. + */ +typedef enum { + VIPS_REGION_SHRINK_MEAN, + VIPS_REGION_SHRINK_MEDIAN, + VIPS_REGION_SHRINK_MODE, + VIPS_REGION_SHRINK_LAST +} VipsRegionShrink; + /* Sub-area of image. */ typedef struct _VipsRegion { @@ -115,13 +130,14 @@ void vips_region_paint( VipsRegion *reg, const VipsRect *r, int value ); void vips_region_paint_pel( VipsRegion *reg, const VipsRect *r, const VipsPel *ink ); void vips_region_black( VipsRegion *reg ); -void vips_region_copy( VipsRegion *reg, VipsRegion *dest, +void vips_region_copy( VipsRegion *reg, VipsRegion *dest, const VipsRect *r, int x, int y ); -int vips_region_shrink( VipsRegion *from, - VipsRegion *to, const VipsRect *target ); +int vips_region_shrink( VipsRegion *from, + VipsRegion *to, const VipsRect *target, + VipsRegionShrink method ); int vips_region_prepare( VipsRegion *reg, const VipsRect *r ); -int vips_region_prepare_to( VipsRegion *reg, +int vips_region_prepare_to( VipsRegion *reg, VipsRegion *dest, const VipsRect *r, int x, int y ); void vips_region_invalidate( VipsRegion *reg ); diff --git a/libvips/iofuncs/Makefile.am b/libvips/iofuncs/Makefile.am index 7e57b716..76115ba8 100644 --- a/libvips/iofuncs/Makefile.am +++ b/libvips/iofuncs/Makefile.am @@ -70,7 +70,8 @@ vips_scan_headers = \ ${top_srcdir}/libvips/include/vips/morphology.h \ ${top_srcdir}/libvips/include/vips/draw.h \ ${top_srcdir}/libvips/include/vips/basic.h \ - ${top_srcdir}/libvips/include/vips/object.h + ${top_srcdir}/libvips/include/vips/object.h \ + ${top_srcdir}/libvips/include/vips/region.h enumtypes.c: $(vips_scan_headers) Makefile.am glib-mkenums --template enumtemplate $(vips_scan_headers) > enumtypes.c diff --git a/libvips/iofuncs/enumtypes.c b/libvips/iofuncs/enumtypes.c index a23424bc..f633b27b 100644 --- a/libvips/iofuncs/enumtypes.c +++ b/libvips/iofuncs/enumtypes.c @@ -866,6 +866,26 @@ vips_operation_flags_get_type( void ) return( etype ); } +/* enumerations from "../../libvips/include/vips/region.h" */ +GType +vips_region_shrink_get_type( void ) +{ + static GType etype = 0; + + if( etype == 0 ) { + static const GEnumValue values[] = { + {VIPS_REGION_SHRINK_MEAN, "VIPS_REGION_SHRINK_MEAN", "mean"}, + {VIPS_REGION_SHRINK_MEDIAN, "VIPS_REGION_SHRINK_MEDIAN", "median"}, + {VIPS_REGION_SHRINK_MODE, "VIPS_REGION_SHRINK_MODE", "mode"}, + {VIPS_REGION_SHRINK_LAST, "VIPS_REGION_SHRINK_LAST", "last"}, + {0, NULL, NULL} + }; + + etype = g_enum_register_static( "VipsRegionShrink", values ); + } + + return( etype ); +} /* enumerations from "../../libvips/include/vips/resample.h" */ GType vips_kernel_get_type( void ) diff --git a/libvips/iofuncs/region.c b/libvips/iofuncs/region.c index 48337d44..6c9c8e35 100644 --- a/libvips/iofuncs/region.c +++ b/libvips/iofuncs/region.c @@ -1166,7 +1166,7 @@ vips_region_shrink_labpack( VipsRegion *from, } } -#define SHRINK_TYPE_INT( TYPE ) \ +#define SHRINK_TYPE_MEAN_INT( TYPE ) \ for( x = 0; x < target->width; x++ ) { \ TYPE *tp = (TYPE *) p; \ TYPE *tp1 = (TYPE *) (p + ls); \ @@ -1185,7 +1185,7 @@ vips_region_shrink_labpack( VipsRegion *from, q += ps; \ } -#define SHRINK_TYPE_FLOAT( TYPE ) \ +#define SHRINK_TYPE_MEAN_FLOAT( TYPE ) \ for( x = 0; x < target->width; x++ ) { \ TYPE *tp = (TYPE *) p; \ TYPE *tp1 = (TYPE *) (p + ls); \ @@ -1204,10 +1204,69 @@ vips_region_shrink_labpack( VipsRegion *from, q += ps; \ } +/* This method is implemented so as to perform well and to always select an + * output pixel from one of the input pixels. As such we make only the + * following guarantees: + * + * ONLY works for non-complex uncoded images pixel types + * ALWAYS draws from the input values + * NEVER interpolates + * NOT stable with respect to the ordered set of input values + * IS stable with respect to the initial arrangement of input values + */ +#define SHRINK_TYPE_MEDIAN( TYPE ) \ + for( x = 0; x < target->width; x++ ) { \ + TYPE *tp = (TYPE *) p; \ + TYPE *tp1 = (TYPE *) (p + ls); \ + TYPE *tq = (TYPE *) q; \ + \ + for( z = 0; z < nb; z++ ) { \ + tq[z] = VIPS_MIN( \ + VIPS_MAX( tp[z], tp[z + nb] ), \ + VIPS_MAX( tp1[z], tp1[z + nb] ) \ + ); \ + } \ + \ + /* Move on two pels in input. \ + */ \ + p += ps << 1; \ + q += ps; \ + } + +/* This method is implemented so as to perform well and to always select an + * output pixel from one of the input pixels. As such we make only the + * following guarantees: + * + * ONLY works for non-complex uncoded images pixel types + * ALWAYS draws from the input values + * NEVER interpolates + * NOT stable with respect to the ordered set of input values + * IS stable with respect to the initial arrangement of input values + */ +#define SHRINK_TYPE_MODE( TYPE ) \ + for( x = 0; x < target->width; x++ ) { \ + TYPE *tp = (TYPE *) p; \ + TYPE *tp1 = (TYPE *) (p + ls); \ + TYPE *tq = (TYPE *) q; \ + \ + for( z = 0; z < nb; z++ ) { \ + TYPE v[] = {tp[z], tp[z + nb], tp1[z], tp1[z + nb]}; \ + int b0 = (v[0] == v[1]) | (v[0] == v[2]) | (v[0] == v[3]); \ + int b1 = (v[1] == v[0]) | (v[1] == v[2]) | (v[1] == v[3]); \ + int index = ((~b0) & 0x1) + (~(b0 ^ b1) & 0x1); \ + tq[z] = v[index]; \ + } \ + \ + /* Move on two pels in input. \ + */ \ + p += ps << 1; \ + q += ps; \ + } + /* Generate area @target in @to using pixels in @from. Non-complex. */ static void -vips_region_shrink_uncoded( VipsRegion *from, +vips_region_shrink_uncoded_mean( VipsRegion *from, VipsRegion *to, const VipsRect *target ) { int ls = VIPS_REGION_LSKIP( from ); @@ -1225,22 +1284,22 @@ vips_region_shrink_uncoded( VipsRegion *from, /* Process this line of pels. */ switch( from->im->BandFmt ) { - case VIPS_FORMAT_UCHAR: - SHRINK_TYPE_INT( unsigned char ); break; - case VIPS_FORMAT_CHAR: - SHRINK_TYPE_INT( signed char ); break; - case VIPS_FORMAT_USHORT: - SHRINK_TYPE_INT( unsigned short ); break; - case VIPS_FORMAT_SHORT: - SHRINK_TYPE_INT( signed short ); break; - case VIPS_FORMAT_UINT: - SHRINK_TYPE_INT( unsigned int ); break; - case VIPS_FORMAT_INT: - SHRINK_TYPE_INT( signed int ); break; - case VIPS_FORMAT_FLOAT: - SHRINK_TYPE_FLOAT( float ); break; - case VIPS_FORMAT_DOUBLE: - SHRINK_TYPE_FLOAT( double ); break; + case VIPS_FORMAT_UCHAR: + SHRINK_TYPE_MEAN_INT( unsigned char ); break; + case VIPS_FORMAT_CHAR: + SHRINK_TYPE_MEAN_INT( signed char ); break; + case VIPS_FORMAT_USHORT: + SHRINK_TYPE_MEAN_INT( unsigned short ); break; + case VIPS_FORMAT_SHORT: + SHRINK_TYPE_MEAN_INT( signed short ); break; + case VIPS_FORMAT_UINT: + SHRINK_TYPE_MEAN_INT( unsigned int ); break; + case VIPS_FORMAT_INT: + SHRINK_TYPE_MEAN_INT( signed int ); break; + case VIPS_FORMAT_FLOAT: + SHRINK_TYPE_MEAN_FLOAT( float ); break; + case VIPS_FORMAT_DOUBLE: + SHRINK_TYPE_MEAN_FLOAT( double ); break; default: g_assert_not_reached(); @@ -1248,6 +1307,116 @@ vips_region_shrink_uncoded( VipsRegion *from, } } +/* Generate area @target in @to using pixels in @from. Non-complex. + */ +static void +vips_region_shrink_uncoded_median( VipsRegion *from, + VipsRegion *to, const VipsRect *target ) +{ + int ls = VIPS_REGION_LSKIP( from ); + int ps = VIPS_IMAGE_SIZEOF_PEL( from->im ); + int nb = from->im->Bands; + + int x, y, z; + + for( y = 0; y < target->height; y++ ) { + VipsPel *p = VIPS_REGION_ADDR( from, + target->left * 2, (target->top + y) * 2 ); + VipsPel *q = VIPS_REGION_ADDR( to, + target->left, target->top + y ); + + /* Process this line of pels. + */ + switch( from->im->BandFmt ) { + case VIPS_FORMAT_UCHAR: + SHRINK_TYPE_MEDIAN( unsigned char ); break; + case VIPS_FORMAT_CHAR: + SHRINK_TYPE_MEDIAN( signed char ); break; + case VIPS_FORMAT_USHORT: + SHRINK_TYPE_MEDIAN( unsigned short ); break; + case VIPS_FORMAT_SHORT: + SHRINK_TYPE_MEDIAN( signed short ); break; + case VIPS_FORMAT_UINT: + SHRINK_TYPE_MEDIAN( unsigned int ); break; + case VIPS_FORMAT_INT: + SHRINK_TYPE_MEDIAN( signed int ); break; + case VIPS_FORMAT_FLOAT: + SHRINK_TYPE_MEDIAN( float ); break; + case VIPS_FORMAT_DOUBLE: + SHRINK_TYPE_MEDIAN( double ); break; + + default: + g_assert_not_reached(); + } + } +} + +/* Generate area @target in @to using pixels in @from. Non-complex. + */ +static void +vips_region_shrink_uncoded_mode( VipsRegion *from, + VipsRegion *to, const VipsRect *target ) +{ + int ls = VIPS_REGION_LSKIP( from ); + int ps = VIPS_IMAGE_SIZEOF_PEL( from->im ); + int nb = from->im->Bands; + + int x, y, z; + + for( y = 0; y < target->height; y++ ) { + VipsPel *p = VIPS_REGION_ADDR( from, + target->left * 2, (target->top + y) * 2 ); + VipsPel *q = VIPS_REGION_ADDR( to, + target->left, target->top + y ); + + /* Process this line of pels. + */ + switch( from->im->BandFmt ) { + case VIPS_FORMAT_UCHAR: + SHRINK_TYPE_MODE( unsigned char ); break; + case VIPS_FORMAT_CHAR: + SHRINK_TYPE_MODE( signed char ); break; + case VIPS_FORMAT_USHORT: + SHRINK_TYPE_MODE( unsigned short ); break; + case VIPS_FORMAT_SHORT: + SHRINK_TYPE_MODE( signed short ); break; + case VIPS_FORMAT_UINT: + SHRINK_TYPE_MODE( unsigned int ); break; + case VIPS_FORMAT_INT: + SHRINK_TYPE_MODE( signed int ); break; + case VIPS_FORMAT_FLOAT: + SHRINK_TYPE_MODE( float ); break; + case VIPS_FORMAT_DOUBLE: + SHRINK_TYPE_MODE( double ); break; + + default: + g_assert_not_reached(); + } + } +} + +/* Generate area @target in @to using pixels in @from. Non-complex. + */ +static void +vips_region_shrink_uncoded( VipsRegion *from, + VipsRegion *to, const VipsRect *target, VipsRegionShrink method ) +{ + switch( method ) { + case VIPS_REGION_SHRINK_MEAN: + vips_region_shrink_uncoded_mean( from, to, target ); + break; + case VIPS_REGION_SHRINK_MEDIAN: + vips_region_shrink_uncoded_median( from, to, target ); + break; + case VIPS_REGION_SHRINK_MODE: + vips_region_shrink_uncoded_mode( from, to, target ); + break; + + default: + g_assert_not_reached(); + } +} + /* No point having an int path, this will always be horribly slow. */ #define SHRINK_ALPHA_TYPE( TYPE ) { \ @@ -1333,9 +1502,10 @@ vips_region_shrink_alpha( VipsRegion *from, /** * vips_region_shrink: - * @from: source region - * @to: (inout): destination region + * @from: source region + * @to: (inout): destination region * @target: #VipsRect of pixels you need to copy + * @method: #VipsRegionShrink method to use when generating target pixels * * Write the pixels @target in @to from the x2 larger area in @from. * Non-complex uncoded images and LABQ only. Images with alpha (see @@ -1344,7 +1514,8 @@ vips_region_shrink_alpha( VipsRegion *from, * See also: vips_region_copy(). */ int -vips_region_shrink( VipsRegion *from, VipsRegion *to, const VipsRect *target ) +vips_region_shrink( VipsRegion *from, VipsRegion *to, const VipsRect *target, + VipsRegionShrink method ) { VipsImage *image = from->im; @@ -1355,10 +1526,10 @@ vips_region_shrink( VipsRegion *from, VipsRegion *to, const VipsRect *target ) if( vips_check_noncomplex( "vips_region_shrink", image ) ) return( -1 ); - if( vips_image_hasalpha( image ) ) + if( vips_image_hasalpha( image ) ) vips_region_shrink_alpha( from, to, target ); else - vips_region_shrink_uncoded( from, to, target ); + vips_region_shrink_uncoded( from, to, target, method ); } else vips_region_shrink_labpack( from, to, target );