redo im_rank() as a class
This commit is contained in:
parent
cb96ed814e
commit
cd7fcf5cfe
@ -37,7 +37,7 @@
|
|||||||
- vips_colourspace() allows B_W, GREY16, RGB16 as source / target
|
- vips_colourspace() allows B_W, GREY16, RGB16 as source / target
|
||||||
- added vips_thread_shutdown(), thanks Lovell
|
- added vips_thread_shutdown(), thanks Lovell
|
||||||
- vips_linear() has a uchar output mode
|
- vips_linear() has a uchar output mode
|
||||||
- redone im_cntlines() as a class
|
- redone im_cntlines(), im_rank() as classes
|
||||||
|
|
||||||
9/1/14 started 7.36.6
|
9/1/14 started 7.36.6
|
||||||
- fix some clang compiler warnings
|
- fix some clang compiler warnings
|
||||||
|
@ -4497,3 +4497,20 @@ im_cntlines( VipsImage *im, double *nolines, int flag )
|
|||||||
VIPS_DIRECTION_HORIZONTAL : VIPS_DIRECTION_VERTICAL,
|
VIPS_DIRECTION_HORIZONTAL : VIPS_DIRECTION_VERTICAL,
|
||||||
NULL ) );
|
NULL ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
im_rank( IMAGE *in, IMAGE *out, int width, int height, int index )
|
||||||
|
{
|
||||||
|
VipsImage *x;
|
||||||
|
|
||||||
|
if( vips_rank( in, &x, width, height, index, NULL ) )
|
||||||
|
return( -1 );
|
||||||
|
|
||||||
|
if( im_copy( x, out ) ) {
|
||||||
|
g_object_unref( x );
|
||||||
|
return( -1 );
|
||||||
|
}
|
||||||
|
g_object_unref( x );
|
||||||
|
|
||||||
|
return( 0 );
|
||||||
|
}
|
||||||
|
@ -42,12 +42,15 @@ int vips_countlines( VipsImage *in, double *nolines,
|
|||||||
VipsDirection direction, ... )
|
VipsDirection direction, ... )
|
||||||
__attribute__((sentinel));
|
__attribute__((sentinel));
|
||||||
|
|
||||||
|
int vips_rank( VipsImage *in, VipsImage **out,
|
||||||
|
int width, int height, int index, ... )
|
||||||
|
__attribute__((sentinel));
|
||||||
|
int vips_median( VipsImage *in, VipsImage **out, int size, ... )
|
||||||
|
__attribute__((sentinel));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
int im_rank( VipsImage *in, VipsImage *out, int width, int height, int index );
|
|
||||||
|
|
||||||
int im_zerox( VipsImage *in, VipsImage *out, int sign );
|
int im_zerox( VipsImage *in, VipsImage *out, int sign );
|
||||||
int im_label_regions( VipsImage *test, VipsImage *mask, int *segments );
|
int im_label_regions( VipsImage *test, VipsImage *mask, int *segments );
|
||||||
|
|
||||||
|
@ -976,6 +976,7 @@ int im_fractsurf( VipsImage *out, int size, double frd );
|
|||||||
int im_phasecor_fft( VipsImage *in1, VipsImage *in2, VipsImage *out );
|
int im_phasecor_fft( VipsImage *in1, VipsImage *in2, VipsImage *out );
|
||||||
|
|
||||||
int im_cntlines( VipsImage *im, double *nolines, int flag );
|
int im_cntlines( VipsImage *im, double *nolines, int flag );
|
||||||
|
int im_rank( VipsImage *in, VipsImage *out, int width, int height, int index );
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,8 @@ noinst_LTLIBRARIES = libmorphology.la
|
|||||||
libmorphology_la_SOURCES = \
|
libmorphology_la_SOURCES = \
|
||||||
morphology.c \
|
morphology.c \
|
||||||
countlines.c \
|
countlines.c \
|
||||||
|
rank.c \
|
||||||
hitmiss.c\
|
hitmiss.c\
|
||||||
im_rank.c \
|
|
||||||
im_zerox.c \
|
im_zerox.c \
|
||||||
morph_dispatch.c \
|
morph_dispatch.c \
|
||||||
im_label_regions.c
|
im_label_regions.c
|
||||||
|
@ -1,430 +0,0 @@
|
|||||||
/* Rank filter.
|
|
||||||
*
|
|
||||||
* Author: JC
|
|
||||||
* Written on: 19/8/96
|
|
||||||
* Modified on:
|
|
||||||
* JC 20/8/96
|
|
||||||
* - now uses insert-sort rather than bubble-sort
|
|
||||||
* - now works for any non-complex type
|
|
||||||
* JC 22/6/01
|
|
||||||
* - oops, sanity check on n wrong
|
|
||||||
* JC 28/8/03
|
|
||||||
* - cleanups
|
|
||||||
* - better selection algorithm ... same speed for 3x3, about 3x faster
|
|
||||||
* for 5x5, faster still for larger windows
|
|
||||||
* - index from zero for consistency with other parts of vips
|
|
||||||
* 7/4/04
|
|
||||||
* - now uses im_embed() with edge stretching on the input, not
|
|
||||||
* the output
|
|
||||||
* - sets Xoffset / Yoffset
|
|
||||||
* 7/10/04
|
|
||||||
* - oops, im_embed() size was wrong
|
|
||||||
* 10/11/10
|
|
||||||
* - cleanups
|
|
||||||
* - gtk-doc
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifdef HAVE_CONFIG_H
|
|
||||||
#include <config.h>
|
|
||||||
#endif /*HAVE_CONFIG_H*/
|
|
||||||
#include <vips/intl.h>
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <assert.h>
|
|
||||||
|
|
||||||
#include <vips/vips.h>
|
|
||||||
|
|
||||||
/* Global state: save our parameters here.
|
|
||||||
*/
|
|
||||||
typedef struct {
|
|
||||||
IMAGE *in, *out; /* Images we run */
|
|
||||||
int xsize, ysize; /* Window size */
|
|
||||||
int index; /* Element select */
|
|
||||||
int n; /* xsize * ysize */
|
|
||||||
} RankInfo;
|
|
||||||
|
|
||||||
/* Sequence value: just the array we sort in.
|
|
||||||
*/
|
|
||||||
typedef struct {
|
|
||||||
REGION *ir;
|
|
||||||
VipsPel *sort;
|
|
||||||
} SeqInfo;
|
|
||||||
|
|
||||||
/* Free a sequence value.
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
rank_stop( void *vseq, void *a, void *b )
|
|
||||||
{
|
|
||||||
SeqInfo *seq = (SeqInfo *) vseq;
|
|
||||||
|
|
||||||
IM_FREEF( im_region_free, seq->ir );
|
|
||||||
|
|
||||||
return( 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Rank start function.
|
|
||||||
*/
|
|
||||||
static void *
|
|
||||||
rank_start( IMAGE *out, void *a, void *b )
|
|
||||||
{
|
|
||||||
IMAGE *in = (IMAGE *) a;
|
|
||||||
RankInfo *rnk = (RankInfo *) b;
|
|
||||||
SeqInfo *seq;
|
|
||||||
|
|
||||||
if( !(seq = IM_NEW( out, SeqInfo )) )
|
|
||||||
return( NULL );
|
|
||||||
|
|
||||||
/* Init!
|
|
||||||
*/
|
|
||||||
seq->ir = NULL;
|
|
||||||
seq->sort = NULL;
|
|
||||||
|
|
||||||
/* Attach region and arrays.
|
|
||||||
*/
|
|
||||||
seq->ir = im_region_create( in );
|
|
||||||
seq->sort = IM_ARRAY( out,
|
|
||||||
IM_IMAGE_SIZEOF_ELEMENT( in ) * rnk->n, VipsPel );
|
|
||||||
if( !seq->ir || !seq->sort ) {
|
|
||||||
rank_stop( seq, in, rnk );
|
|
||||||
return( NULL );
|
|
||||||
}
|
|
||||||
|
|
||||||
return( (void *) seq );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Inner loop for select-sorting TYPE.
|
|
||||||
*/
|
|
||||||
#define LOOP_SELECT( TYPE ) { \
|
|
||||||
TYPE *q = (TYPE *) IM_REGION_ADDR( or, le, y ); \
|
|
||||||
TYPE *p = (TYPE *) IM_REGION_ADDR( ir, le, y ); \
|
|
||||||
TYPE *sort = (TYPE *) seq->sort; \
|
|
||||||
TYPE a; \
|
|
||||||
\
|
|
||||||
for( x = 0; x < sz; x++ ) { \
|
|
||||||
TYPE *d = p + x; \
|
|
||||||
\
|
|
||||||
/* Copy window into sort[].
|
|
||||||
*/ \
|
|
||||||
for( k = 0, j = 0; j < rnk->ysize; j++ ) { \
|
|
||||||
for( i = 0; i < eaw; i += bands, k++ ) \
|
|
||||||
sort[k] = d[i]; \
|
|
||||||
d += ls; \
|
|
||||||
} \
|
|
||||||
\
|
|
||||||
/* Rearrange sort[] to make the index-th element the index-th
|
|
||||||
* smallest, adapted from Numerical Recipes in C.
|
|
||||||
*/ \
|
|
||||||
lower = 0; /* Range we know the result lies in */ \
|
|
||||||
upper = rnk->n - 1; \
|
|
||||||
for(;;) { \
|
|
||||||
if( upper - lower < 2 ) { \
|
|
||||||
/* 1 or 2 elements left.
|
|
||||||
*/ \
|
|
||||||
if( upper - lower == 1 && \
|
|
||||||
sort[lower] > sort[upper] ) \
|
|
||||||
IM_SWAP( TYPE, \
|
|
||||||
sort[lower], sort[upper] ); \
|
|
||||||
break; \
|
|
||||||
} \
|
|
||||||
else { \
|
|
||||||
/* Pick mid-point of remaining elements.
|
|
||||||
*/ \
|
|
||||||
mid = (lower + upper) >> 1; \
|
|
||||||
\
|
|
||||||
/* Sort lower/mid/upper elements, hold
|
|
||||||
* midpoint in sort[lower + 1] for
|
|
||||||
* partitioning.
|
|
||||||
*/ \
|
|
||||||
IM_SWAP( TYPE, sort[lower + 1], sort[mid] ); \
|
|
||||||
if( sort[lower] > sort[upper] ) \
|
|
||||||
IM_SWAP( TYPE, \
|
|
||||||
sort[lower], sort[upper] ); \
|
|
||||||
if( sort[lower + 1] > sort[upper] ) \
|
|
||||||
IM_SWAP( TYPE, \
|
|
||||||
sort[lower + 1], sort[upper] );\
|
|
||||||
if( sort[lower] > sort[lower + 1] ) \
|
|
||||||
IM_SWAP( TYPE, \
|
|
||||||
sort[lower], sort[lower + 1] );\
|
|
||||||
\
|
|
||||||
i = lower + 1; \
|
|
||||||
j = upper; \
|
|
||||||
a = sort[lower + 1]; \
|
|
||||||
\
|
|
||||||
for(;;) { \
|
|
||||||
/* Search for out of order elements.
|
|
||||||
*/ \
|
|
||||||
do \
|
|
||||||
i++; \
|
|
||||||
while( sort[i] < a ); \
|
|
||||||
do \
|
|
||||||
j--; \
|
|
||||||
while( sort[j] > a ); \
|
|
||||||
if( j < i ) \
|
|
||||||
break; \
|
|
||||||
IM_SWAP( TYPE, sort[i], sort[j] ); \
|
|
||||||
} \
|
|
||||||
\
|
|
||||||
/* Replace mid element.
|
|
||||||
*/ \
|
|
||||||
sort[lower + 1] = sort[j]; \
|
|
||||||
sort[j] = a; \
|
|
||||||
\
|
|
||||||
/* Move to partition with the kth element.
|
|
||||||
*/ \
|
|
||||||
if( j >= rnk->index ) \
|
|
||||||
upper = j - 1; \
|
|
||||||
if( j <= rnk->index ) \
|
|
||||||
lower = i; \
|
|
||||||
} \
|
|
||||||
} \
|
|
||||||
\
|
|
||||||
q[x] = sort[rnk->index]; \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Loop for find max of window.
|
|
||||||
*/
|
|
||||||
#define LOOP_MAX( TYPE ) { \
|
|
||||||
TYPE *q = (TYPE *) IM_REGION_ADDR( or, le, y ); \
|
|
||||||
TYPE *p = (TYPE *) IM_REGION_ADDR( ir, le, y ); \
|
|
||||||
\
|
|
||||||
for( x = 0; x < sz; x++ ) { \
|
|
||||||
TYPE *d = &p[x]; \
|
|
||||||
TYPE max; \
|
|
||||||
\
|
|
||||||
max = *d; \
|
|
||||||
for( j = 0; j < rnk->ysize; j++ ) { \
|
|
||||||
TYPE *e = d; \
|
|
||||||
\
|
|
||||||
for( i = 0; i < rnk->xsize; i++ ) { \
|
|
||||||
if( *e > max ) \
|
|
||||||
max = *e; \
|
|
||||||
\
|
|
||||||
e += bands; \
|
|
||||||
} \
|
|
||||||
\
|
|
||||||
d += ls; \
|
|
||||||
} \
|
|
||||||
\
|
|
||||||
q[x] = max; \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Loop for find min of window.
|
|
||||||
*/
|
|
||||||
#define LOOP_MIN( TYPE ) { \
|
|
||||||
TYPE *q = (TYPE *) IM_REGION_ADDR( or, le, y ); \
|
|
||||||
TYPE *p = (TYPE *) IM_REGION_ADDR( ir, le, y ); \
|
|
||||||
\
|
|
||||||
for( x = 0; x < sz; x++ ) { \
|
|
||||||
TYPE *d = &p[x]; \
|
|
||||||
TYPE min; \
|
|
||||||
\
|
|
||||||
min = *d; \
|
|
||||||
for( j = 0; j < rnk->ysize; j++ ) { \
|
|
||||||
TYPE *e = d; \
|
|
||||||
\
|
|
||||||
for( i = 0; i < rnk->xsize; i++ ) { \
|
|
||||||
if( *e < min ) \
|
|
||||||
min = *e; \
|
|
||||||
\
|
|
||||||
e += bands; \
|
|
||||||
} \
|
|
||||||
\
|
|
||||||
d += ls; \
|
|
||||||
} \
|
|
||||||
\
|
|
||||||
q[x] = min; \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define SWITCH( OPERATION ) \
|
|
||||||
switch( rnk->out->BandFmt ) { \
|
|
||||||
case IM_BANDFMT_UCHAR: OPERATION( unsigned char ); break; \
|
|
||||||
case IM_BANDFMT_CHAR: OPERATION( signed char ); break; \
|
|
||||||
case IM_BANDFMT_USHORT: OPERATION( unsigned short ); break; \
|
|
||||||
case IM_BANDFMT_SHORT: OPERATION( signed short ); break; \
|
|
||||||
case IM_BANDFMT_UINT: OPERATION( unsigned int ); break; \
|
|
||||||
case IM_BANDFMT_INT: OPERATION( signed int ); break; \
|
|
||||||
case IM_BANDFMT_FLOAT: OPERATION( float ); break; \
|
|
||||||
case IM_BANDFMT_DOUBLE: OPERATION( double ); break; \
|
|
||||||
\
|
|
||||||
default: \
|
|
||||||
assert( 0 ); \
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Rank of a REGION.
|
|
||||||
*/
|
|
||||||
static int
|
|
||||||
rank_gen( REGION *or, void *vseq, void *a, void *b )
|
|
||||||
{
|
|
||||||
SeqInfo *seq = (SeqInfo *) vseq;
|
|
||||||
IMAGE *in = (IMAGE *) a;
|
|
||||||
RankInfo *rnk = (RankInfo *) b;
|
|
||||||
REGION *ir = seq->ir;
|
|
||||||
|
|
||||||
Rect *r = &or->valid;
|
|
||||||
Rect s;
|
|
||||||
int le = r->left;
|
|
||||||
int to = r->top;
|
|
||||||
int bo = IM_RECT_BOTTOM(r);
|
|
||||||
int sz = IM_REGION_N_ELEMENTS( or );
|
|
||||||
|
|
||||||
int ls;
|
|
||||||
int bands = in->Bands;
|
|
||||||
int eaw = rnk->xsize * bands; /* elements across window */
|
|
||||||
|
|
||||||
int x, y;
|
|
||||||
int i, j, k;
|
|
||||||
int upper, lower, mid;
|
|
||||||
|
|
||||||
/* Prepare the section of the input image we need. A little larger
|
|
||||||
* than the section of the output image we are producing.
|
|
||||||
*/
|
|
||||||
s = *r;
|
|
||||||
s.width += rnk->xsize - 1;
|
|
||||||
s.height += rnk->ysize - 1;
|
|
||||||
if( im_prepare( ir, &s ) )
|
|
||||||
return( -1 );
|
|
||||||
ls = IM_REGION_LSKIP( ir ) / IM_IMAGE_SIZEOF_ELEMENT( in );
|
|
||||||
|
|
||||||
for( y = to; y < bo; y++ ) {
|
|
||||||
if( rnk->index == 0 )
|
|
||||||
SWITCH( LOOP_MIN )
|
|
||||||
else if( rnk->index == rnk->n - 1 )
|
|
||||||
SWITCH( LOOP_MAX )
|
|
||||||
else
|
|
||||||
SWITCH( LOOP_SELECT ) }
|
|
||||||
|
|
||||||
return( 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Rank filter.
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
im_rank_raw( IMAGE *in, IMAGE *out, int xsize, int ysize, int index )
|
|
||||||
{
|
|
||||||
RankInfo *rnk;
|
|
||||||
|
|
||||||
if( im_piocheck( in, out ) ||
|
|
||||||
im_check_uncoded( "im_rank", in ) ||
|
|
||||||
im_check_noncomplex( "im_rank", in ) )
|
|
||||||
return( -1 );
|
|
||||||
if( xsize > 1000 || ysize > 1000 || xsize <= 0 || ysize <= 0 ||
|
|
||||||
index < 0 || index > xsize * ysize - 1 ) {
|
|
||||||
im_error( "im_rank", "%s", _( "bad parameters" ) );
|
|
||||||
return( -1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Save parameters.
|
|
||||||
*/
|
|
||||||
if( !(rnk = IM_NEW( out, RankInfo )) )
|
|
||||||
return( -1 );
|
|
||||||
rnk->in = in;
|
|
||||||
rnk->out = out;
|
|
||||||
rnk->xsize = xsize;
|
|
||||||
rnk->ysize = ysize;
|
|
||||||
rnk->index = index;
|
|
||||||
rnk->n = xsize * ysize;
|
|
||||||
|
|
||||||
/* Prepare output. Consider a 7x7 window and a 7x7 image --- the output
|
|
||||||
* would be 1x1.
|
|
||||||
*/
|
|
||||||
if( im_cp_desc( out, in ) )
|
|
||||||
return( -1 );
|
|
||||||
out->Xsize -= xsize - 1;
|
|
||||||
out->Ysize -= ysize - 1;
|
|
||||||
if( out->Xsize <= 0 || out->Ysize <= 0 ) {
|
|
||||||
im_error( "im_rank", "%s", _( "image too small for window" ) );
|
|
||||||
return( -1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( im_demand_hint( out, IM_SMALLTILE, in, NULL ) )
|
|
||||||
return( -1 );
|
|
||||||
|
|
||||||
/* Generate!
|
|
||||||
*/
|
|
||||||
if( im_generate( out, rank_start, rank_gen, rank_stop, in, rnk ) )
|
|
||||||
return( -1 );
|
|
||||||
|
|
||||||
out->Xoffset = -xsize / 2;
|
|
||||||
out->Yoffset = -ysize / 2;
|
|
||||||
|
|
||||||
return( 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* im_rank:
|
|
||||||
* @in: input image
|
|
||||||
* @out: output image
|
|
||||||
* @width: window width
|
|
||||||
* @height: window height
|
|
||||||
* @index: select pixel
|
|
||||||
*
|
|
||||||
* im_rank() does rank filtering on an image. A window of size @width by
|
|
||||||
* @height is passed over the image. At each position, the pixels inside the
|
|
||||||
* window are sorted into ascending order and the pixel at position @index is
|
|
||||||
* output. @index numbers from 0.
|
|
||||||
*
|
|
||||||
* It works for any non-complex image type, with any number of bands.
|
|
||||||
* The input is expanded by copying edge pixels before performing the
|
|
||||||
* operation so that the output image has the same size as the input.
|
|
||||||
* Edge pixels in the output image are therefore only approximate.
|
|
||||||
*
|
|
||||||
* For a median filter with mask size m (3 for 3x3, 5 for 5x5, etc.) use
|
|
||||||
*
|
|
||||||
* im_rank( in, out, m, m, m * m / 2 );
|
|
||||||
*
|
|
||||||
* The special cases n == 0 and n == m * m - 1 are useful dilate and
|
|
||||||
* expand operators.
|
|
||||||
*
|
|
||||||
* See also: im_conv(), im_fastcor().
|
|
||||||
*
|
|
||||||
* Returns: 0 on success, -1 on error
|
|
||||||
*/
|
|
||||||
int
|
|
||||||
im_rank( IMAGE *in, IMAGE *out, int width, int height, int index )
|
|
||||||
{
|
|
||||||
IMAGE *t1;
|
|
||||||
|
|
||||||
if( !(t1 = im_open_local( out, "im_rank", "p" )) ||
|
|
||||||
im_embed( in, t1, 1,
|
|
||||||
width / 2, height / 2,
|
|
||||||
in->Xsize + width - 1, in->Ysize + height - 1 ) ||
|
|
||||||
im_rank_raw( t1, out, width, height, index ) )
|
|
||||||
return( -1 );
|
|
||||||
|
|
||||||
out->Xoffset = 0;
|
|
||||||
out->Yoffset = 0;
|
|
||||||
|
|
||||||
return( 0 );
|
|
||||||
}
|
|
@ -132,6 +132,8 @@ void
|
|||||||
vips_morphology_operation_init( void )
|
vips_morphology_operation_init( void )
|
||||||
{
|
{
|
||||||
extern int vips_countlines_get_type( void );
|
extern int vips_countlines_get_type( void );
|
||||||
|
extern int vips_rank_get_type( void );
|
||||||
|
|
||||||
vips_countlines_get_type();
|
vips_countlines_get_type();
|
||||||
|
vips_rank_get_type();
|
||||||
}
|
}
|
||||||
|
512
libvips/morphology/rank.c
Normal file
512
libvips/morphology/rank.c
Normal file
@ -0,0 +1,512 @@
|
|||||||
|
/* Rank filter.
|
||||||
|
*
|
||||||
|
* Author: JC
|
||||||
|
* Written on: 19/8/96
|
||||||
|
* Modified on:
|
||||||
|
* JC 20/8/96
|
||||||
|
* - now uses insert-sort rather than bubble-sort
|
||||||
|
* - now works for any non-complex type
|
||||||
|
* JC 22/6/01
|
||||||
|
* - oops, sanity check on n wrong
|
||||||
|
* JC 28/8/03
|
||||||
|
* - cleanups
|
||||||
|
* - better selection algorithm ... same speed for 3x3, about 3x faster
|
||||||
|
* for 5x5, faster still for larger windows
|
||||||
|
* - index from zero for consistency with other parts of vips
|
||||||
|
* 7/4/04
|
||||||
|
* - now uses im_embed() with edge stretching on the input, not
|
||||||
|
* the output
|
||||||
|
* - sets Xoffset / Yoffset
|
||||||
|
* 7/10/04
|
||||||
|
* - oops, im_embed() size was wrong
|
||||||
|
* 10/11/10
|
||||||
|
* - cleanups
|
||||||
|
* - gtk-doc
|
||||||
|
* 17/1/14
|
||||||
|
* - redone 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
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#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 <math.h>
|
||||||
|
|
||||||
|
#include <vips/vips.h>
|
||||||
|
|
||||||
|
#include "pmorphology.h"
|
||||||
|
|
||||||
|
typedef struct _VipsRank {
|
||||||
|
VipsMorphology parent_instance;
|
||||||
|
|
||||||
|
VipsImage *out;
|
||||||
|
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
int index;
|
||||||
|
|
||||||
|
int n;
|
||||||
|
|
||||||
|
} VipsRank;
|
||||||
|
|
||||||
|
typedef VipsMorphologyClass VipsRankClass;
|
||||||
|
|
||||||
|
G_DEFINE_TYPE( VipsRank, vips_rank, VIPS_TYPE_MORPHOLOGY );
|
||||||
|
|
||||||
|
/* Sequence value: just the array we sort in.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
VipsRegion *ir;
|
||||||
|
VipsPel *sort;
|
||||||
|
} VipsRankSequence;
|
||||||
|
|
||||||
|
static int
|
||||||
|
vips_rank_stop( void *vseq, void *a, void *b )
|
||||||
|
{
|
||||||
|
VipsRankSequence *seq = (VipsRankSequence *) vseq;
|
||||||
|
|
||||||
|
VIPS_FREEF( g_object_unref, seq->ir );
|
||||||
|
|
||||||
|
return( 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *
|
||||||
|
vips_rank_start( IMAGE *out, void *a, void *b )
|
||||||
|
{
|
||||||
|
VipsImage *in = (VipsImage *) a;
|
||||||
|
VipsRank *rank = (VipsRank *) b;
|
||||||
|
VipsRankSequence *seq;
|
||||||
|
|
||||||
|
if( !(seq = VIPS_NEW( out, VipsRankSequence )) )
|
||||||
|
return( NULL );
|
||||||
|
seq->ir = NULL;
|
||||||
|
seq->sort = NULL;
|
||||||
|
|
||||||
|
seq->ir = vips_region_new( in );
|
||||||
|
if( !(seq->sort = VIPS_ARRAY( out,
|
||||||
|
VIPS_IMAGE_SIZEOF_ELEMENT( in ) * rank->n, VipsPel )) ) {
|
||||||
|
vips_rank_stop( seq, in, rank );
|
||||||
|
return( NULL );
|
||||||
|
}
|
||||||
|
|
||||||
|
return( (void *) seq );
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inner loop for select-sorting TYPE.
|
||||||
|
*/
|
||||||
|
#define LOOP_SELECT( TYPE ) { \
|
||||||
|
TYPE *q = (TYPE *) VIPS_REGION_ADDR( or, r->left, r->top + y ); \
|
||||||
|
TYPE *p = (TYPE *) VIPS_REGION_ADDR( ir, r->left, r->top + y ); \
|
||||||
|
TYPE *sort = (TYPE *) seq->sort; \
|
||||||
|
TYPE a; \
|
||||||
|
\
|
||||||
|
for( x = 0; x < sz; x++ ) { \
|
||||||
|
TYPE *d = p + x; \
|
||||||
|
\
|
||||||
|
/* Copy window into sort[].
|
||||||
|
*/ \
|
||||||
|
for( k = 0, j = 0; j < rank->height; j++ ) { \
|
||||||
|
for( i = 0; i < eaw; i += bands, k++ ) \
|
||||||
|
sort[k] = d[i]; \
|
||||||
|
d += ls; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
/* Rearrange sort[] to make the index-th element the index-th
|
||||||
|
* smallest, adapted from Numerical Recipes in C.
|
||||||
|
*/ \
|
||||||
|
lower = 0; /* Range we know the result lies in */ \
|
||||||
|
upper = rank->n - 1; \
|
||||||
|
for(;;) { \
|
||||||
|
if( upper - lower < 2 ) { \
|
||||||
|
/* 1 or 2 elements left.
|
||||||
|
*/ \
|
||||||
|
if( upper - lower == 1 && \
|
||||||
|
sort[lower] > sort[upper] ) \
|
||||||
|
VIPS_SWAP( TYPE, \
|
||||||
|
sort[lower], sort[upper] ); \
|
||||||
|
break; \
|
||||||
|
} \
|
||||||
|
else { \
|
||||||
|
/* Pick mid-point of remaining elements.
|
||||||
|
*/ \
|
||||||
|
mid = (lower + upper) >> 1; \
|
||||||
|
\
|
||||||
|
/* Sort lower/mid/upper elements, hold
|
||||||
|
* midpoint in sort[lower + 1] for
|
||||||
|
* partitioning.
|
||||||
|
*/ \
|
||||||
|
VIPS_SWAP( TYPE, sort[lower + 1], sort[mid] ); \
|
||||||
|
if( sort[lower] > sort[upper] ) \
|
||||||
|
VIPS_SWAP( TYPE, \
|
||||||
|
sort[lower], sort[upper] ); \
|
||||||
|
if( sort[lower + 1] > sort[upper] ) \
|
||||||
|
VIPS_SWAP( TYPE, \
|
||||||
|
sort[lower + 1], sort[upper] );\
|
||||||
|
if( sort[lower] > sort[lower + 1] ) \
|
||||||
|
VIPS_SWAP( TYPE, \
|
||||||
|
sort[lower], sort[lower + 1] );\
|
||||||
|
\
|
||||||
|
i = lower + 1; \
|
||||||
|
j = upper; \
|
||||||
|
a = sort[lower + 1]; \
|
||||||
|
\
|
||||||
|
for(;;) { \
|
||||||
|
/* Search for out of order elements.
|
||||||
|
*/ \
|
||||||
|
do \
|
||||||
|
i++; \
|
||||||
|
while( sort[i] < a ); \
|
||||||
|
do \
|
||||||
|
j--; \
|
||||||
|
while( sort[j] > a ); \
|
||||||
|
if( j < i ) \
|
||||||
|
break; \
|
||||||
|
VIPS_SWAP( TYPE, sort[i], sort[j] ); \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
/* Replace mid element.
|
||||||
|
*/ \
|
||||||
|
sort[lower + 1] = sort[j]; \
|
||||||
|
sort[j] = a; \
|
||||||
|
\
|
||||||
|
/* Move to partition with the kth element.
|
||||||
|
*/ \
|
||||||
|
if( j >= rank->index ) \
|
||||||
|
upper = j - 1; \
|
||||||
|
if( j <= rank->index ) \
|
||||||
|
lower = i; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
q[x] = sort[rank->index]; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loop for find max of window.
|
||||||
|
*/
|
||||||
|
#define LOOP_MAX( TYPE ) { \
|
||||||
|
TYPE *q = (TYPE *) VIPS_REGION_ADDR( or, r->left, r->top + y ); \
|
||||||
|
TYPE *p = (TYPE *) VIPS_REGION_ADDR( ir, r->left, r->top + y ); \
|
||||||
|
\
|
||||||
|
for( x = 0; x < sz; x++ ) { \
|
||||||
|
TYPE *d = &p[x]; \
|
||||||
|
TYPE max; \
|
||||||
|
\
|
||||||
|
max = *d; \
|
||||||
|
for( j = 0; j < rank->height; j++ ) { \
|
||||||
|
TYPE *e = d; \
|
||||||
|
\
|
||||||
|
for( i = 0; i < rank->width; i++ ) { \
|
||||||
|
if( *e > max ) \
|
||||||
|
max = *e; \
|
||||||
|
\
|
||||||
|
e += bands; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
d += ls; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
q[x] = max; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loop for find min of window.
|
||||||
|
*/
|
||||||
|
#define LOOP_MIN( TYPE ) { \
|
||||||
|
TYPE *q = (TYPE *) VIPS_REGION_ADDR( or, r->left, r->top + y ); \
|
||||||
|
TYPE *p = (TYPE *) VIPS_REGION_ADDR( ir, r->left, r->top + y ); \
|
||||||
|
\
|
||||||
|
for( x = 0; x < sz; x++ ) { \
|
||||||
|
TYPE *d = &p[x]; \
|
||||||
|
TYPE min; \
|
||||||
|
\
|
||||||
|
min = *d; \
|
||||||
|
for( j = 0; j < rank->height; j++ ) { \
|
||||||
|
TYPE *e = d; \
|
||||||
|
\
|
||||||
|
for( i = 0; i < rank->width; i++ ) { \
|
||||||
|
if( *e < min ) \
|
||||||
|
min = *e; \
|
||||||
|
\
|
||||||
|
e += bands; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
d += ls; \
|
||||||
|
} \
|
||||||
|
\
|
||||||
|
q[x] = min; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define SWITCH( OPERATION ) \
|
||||||
|
switch( rank->out->BandFmt ) { \
|
||||||
|
case VIPS_FORMAT_UCHAR: OPERATION( unsigned char ); break; \
|
||||||
|
case VIPS_FORMAT_CHAR: OPERATION( signed char ); break; \
|
||||||
|
case VIPS_FORMAT_USHORT: OPERATION( unsigned short ); break; \
|
||||||
|
case VIPS_FORMAT_SHORT: OPERATION( signed short ); break; \
|
||||||
|
case VIPS_FORMAT_UINT: OPERATION( unsigned int ); break; \
|
||||||
|
case VIPS_FORMAT_INT: OPERATION( signed int ); break; \
|
||||||
|
case VIPS_FORMAT_FLOAT: OPERATION( float ); break; \
|
||||||
|
case VIPS_FORMAT_DOUBLE: OPERATION( double ); break; \
|
||||||
|
\
|
||||||
|
default: \
|
||||||
|
g_assert( 0 ); \
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
vips_rank_generate( VipsRegion *or,
|
||||||
|
void *vseq, void *a, void *b, gboolean *stop )
|
||||||
|
{
|
||||||
|
VipsRect *r = &or->valid;
|
||||||
|
VipsRankSequence *seq = (VipsRankSequence *) vseq;
|
||||||
|
VipsRegion *ir = seq->ir;
|
||||||
|
VipsImage *in = (VipsImage *) a;
|
||||||
|
VipsRank *rank = (VipsRank *) b;
|
||||||
|
int bands = in->Bands;
|
||||||
|
int eaw = rank->width * bands; /* elements across window */
|
||||||
|
int sz = VIPS_REGION_N_ELEMENTS( or );
|
||||||
|
|
||||||
|
VipsRect s;
|
||||||
|
int ls;
|
||||||
|
|
||||||
|
int x, y;
|
||||||
|
int i, j, k;
|
||||||
|
int upper, lower, mid;
|
||||||
|
|
||||||
|
/* Prepare the section of the input image we need. A little larger
|
||||||
|
* than the section of the output image we are producing.
|
||||||
|
*/
|
||||||
|
s = *r;
|
||||||
|
s.width += rank->width - 1;
|
||||||
|
s.height += rank->height - 1;
|
||||||
|
if( vips_region_prepare( ir, &s ) )
|
||||||
|
return( -1 );
|
||||||
|
ls = VIPS_REGION_LSKIP( ir ) / VIPS_IMAGE_SIZEOF_ELEMENT( in );
|
||||||
|
|
||||||
|
for( y = 0; y < r->height; y++ ) {
|
||||||
|
if( rank->index == 0 )
|
||||||
|
SWITCH( LOOP_MIN )
|
||||||
|
else if( rank->index == rank->n - 1 )
|
||||||
|
SWITCH( LOOP_MAX )
|
||||||
|
else
|
||||||
|
SWITCH( LOOP_SELECT ) }
|
||||||
|
|
||||||
|
return( 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
vips_rank_build( VipsObject *object )
|
||||||
|
{
|
||||||
|
VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );
|
||||||
|
VipsMorphology *morphology = VIPS_MORPHOLOGY( object );
|
||||||
|
VipsRank *rank = (VipsRank *) object;
|
||||||
|
VipsImage **t = (VipsImage **) vips_object_local_array( object, 3 );
|
||||||
|
|
||||||
|
VipsImage *in;
|
||||||
|
|
||||||
|
if( VIPS_OBJECT_CLASS( vips_rank_parent_class )->build( object ) )
|
||||||
|
return( -1 );
|
||||||
|
|
||||||
|
in = morphology->in;
|
||||||
|
|
||||||
|
if( vips_check_uncoded( class->nickname, in ) ||
|
||||||
|
vips_check_noncomplex( class->nickname, in ) )
|
||||||
|
return( -1 );
|
||||||
|
if( rank->width > in->Xsize ||
|
||||||
|
rank->height > in->Ysize ) {
|
||||||
|
vips_error( class->nickname, "%s", _( "window too large" ) );
|
||||||
|
return( -1 );
|
||||||
|
}
|
||||||
|
rank->n = rank->width * rank->height;
|
||||||
|
if( rank->index < 0 || rank->index > rank->n - 1 ) {
|
||||||
|
vips_error( class->nickname, "%s", _( "index out of range" ) );
|
||||||
|
return( -1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Expand the input.
|
||||||
|
*/
|
||||||
|
if( vips_embed( in, &t[0],
|
||||||
|
rank->width / 2, rank->height / 2,
|
||||||
|
in->Xsize + rank->width - 1, in->Ysize + rank->height - 1,
|
||||||
|
"extend", VIPS_EXTEND_COPY,
|
||||||
|
NULL ) )
|
||||||
|
return( -1 );
|
||||||
|
in = t[0];
|
||||||
|
|
||||||
|
g_object_set( object, "out", vips_image_new(), NULL );
|
||||||
|
|
||||||
|
/* Set demand hints. FATSTRIP is good for us, as THINSTRIP will cause
|
||||||
|
* too many recalculations on overlaps.
|
||||||
|
*/
|
||||||
|
if( vips_image_pipelinev( rank->out,
|
||||||
|
VIPS_DEMAND_STYLE_FATSTRIP, in, NULL ) )
|
||||||
|
return( -1 );
|
||||||
|
rank->out->Xsize -= rank->width - 1;
|
||||||
|
rank->out->Ysize -= rank->height - 1;
|
||||||
|
|
||||||
|
if( vips_image_generate( rank->out,
|
||||||
|
vips_rank_start,
|
||||||
|
vips_rank_generate,
|
||||||
|
vips_rank_stop,
|
||||||
|
in, rank ) )
|
||||||
|
return( -1 );
|
||||||
|
|
||||||
|
rank->out->Xoffset = 0;
|
||||||
|
rank->out->Yoffset = 0;
|
||||||
|
|
||||||
|
return( 0 );
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
vips_rank_class_init( VipsRankClass *class )
|
||||||
|
{
|
||||||
|
GObjectClass *gobject_class = G_OBJECT_CLASS( class );
|
||||||
|
VipsObjectClass *object_class = (VipsObjectClass *) class;
|
||||||
|
|
||||||
|
gobject_class->set_property = vips_object_set_property;
|
||||||
|
gobject_class->get_property = vips_object_get_property;
|
||||||
|
|
||||||
|
object_class->nickname = "rank";
|
||||||
|
object_class->description = _( "rank filter" );
|
||||||
|
object_class->build = vips_rank_build;
|
||||||
|
|
||||||
|
VIPS_ARG_IMAGE( class, "out", 2,
|
||||||
|
_( "Output" ),
|
||||||
|
_( "Output image" ),
|
||||||
|
VIPS_ARGUMENT_REQUIRED_OUTPUT,
|
||||||
|
G_STRUCT_OFFSET( VipsRank, out ) );
|
||||||
|
|
||||||
|
VIPS_ARG_INT( class, "width", 4,
|
||||||
|
_( "Width" ),
|
||||||
|
_( "Window width in pixels" ),
|
||||||
|
VIPS_ARGUMENT_REQUIRED_INPUT,
|
||||||
|
G_STRUCT_OFFSET( VipsRank, width ),
|
||||||
|
1, 100000, 11 );
|
||||||
|
|
||||||
|
VIPS_ARG_INT( class, "height", 5,
|
||||||
|
_( "Height" ),
|
||||||
|
_( "Window height in pixels" ),
|
||||||
|
VIPS_ARGUMENT_REQUIRED_INPUT,
|
||||||
|
G_STRUCT_OFFSET( VipsRank, height ),
|
||||||
|
1, 100000, 11 );
|
||||||
|
|
||||||
|
VIPS_ARG_INT( class, "index", 6,
|
||||||
|
_( "index" ),
|
||||||
|
_( "Select pixel at index" ),
|
||||||
|
VIPS_ARGUMENT_REQUIRED_INPUT,
|
||||||
|
G_STRUCT_OFFSET( VipsRank, index ),
|
||||||
|
1, 100000000, 50 );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
vips_rank_init( VipsRank *rank )
|
||||||
|
{
|
||||||
|
rank->width = 11;
|
||||||
|
rank->height = 11;
|
||||||
|
rank->index = 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* vips_rank:
|
||||||
|
* @in: input image
|
||||||
|
* @out: output image
|
||||||
|
* @width: width of region
|
||||||
|
* @height: height of region
|
||||||
|
* @index: select pixel
|
||||||
|
* @...: %NULL-terminated list of optional named arguments
|
||||||
|
*
|
||||||
|
* vips_rank() does rank filtering on an image. A window of size @width by
|
||||||
|
* @height is passed over the image. At each position, the pixels inside the
|
||||||
|
* window are sorted into ascending order and the pixel at position @index is
|
||||||
|
* output. @index numbers from 0.
|
||||||
|
*
|
||||||
|
* It works for any non-complex image type, with any number of bands.
|
||||||
|
* The input is expanded by copying edge pixels before performing the
|
||||||
|
* operation so that the output image has the same size as the input.
|
||||||
|
* Edge pixels in the output image are therefore only approximate.
|
||||||
|
*
|
||||||
|
* For a median filter with mask size m (3 for 3x3, 5 for 5x5, etc.) use
|
||||||
|
*
|
||||||
|
* vips_rank( in, out, m, m, m * m / 2 );
|
||||||
|
*
|
||||||
|
* The special cases n == 0 and n == m * m - 1 are useful dilate and
|
||||||
|
* expand operators.
|
||||||
|
*
|
||||||
|
* See also: vips_conv(), vips_median(), vips_spcor().
|
||||||
|
*
|
||||||
|
* Returns: 0 on success, -1 on error
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
vips_rank( VipsImage *in, VipsImage **out,
|
||||||
|
int width, int height, int index, ... )
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
int result;
|
||||||
|
|
||||||
|
va_start( ap, index );
|
||||||
|
result = vips_call_split( "rank", ap, in, out, width, height, index );
|
||||||
|
va_end( ap );
|
||||||
|
|
||||||
|
return( result );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* vips_median:
|
||||||
|
* @in: input image
|
||||||
|
* @out: output image
|
||||||
|
* @size: size of region
|
||||||
|
* @...: %NULL-terminated list of optional named arguments
|
||||||
|
*
|
||||||
|
* A convenience function equivalent to:
|
||||||
|
*
|
||||||
|
* vips_rank( in, out, size, size, (size * size) / 2 );
|
||||||
|
*
|
||||||
|
* See also: vips_rank().
|
||||||
|
*
|
||||||
|
* Returns: 0 on success, -1 on error
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
vips_median( VipsImage *in, VipsImage **out, int size, ... )
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
int result;
|
||||||
|
|
||||||
|
va_start( ap, size );
|
||||||
|
result = vips_call_split( "rank", ap, in, out,
|
||||||
|
size, size, (size * size) / 2 );
|
||||||
|
va_end( ap );
|
||||||
|
|
||||||
|
return( result );
|
||||||
|
}
|
@ -137,8 +137,6 @@ vips_shrink_start( VipsImage *out, void *a, void *b )
|
|||||||
if( !(seq = IM_NEW( out, VipsShrinkSequence )) )
|
if( !(seq = IM_NEW( out, VipsShrinkSequence )) )
|
||||||
return( NULL );
|
return( NULL );
|
||||||
|
|
||||||
/* Init!
|
|
||||||
*/
|
|
||||||
seq->ir = vips_region_new( in );
|
seq->ir = vips_region_new( in );
|
||||||
if( !(seq->sum = (VipsPel *) VIPS_ARRAY( out, in->Bands, double )) ) {
|
if( !(seq->sum = (VipsPel *) VIPS_ARRAY( out, in->Bands, double )) ) {
|
||||||
vips_shrink_stop( seq, in, shrink );
|
vips_shrink_stop( seq, in, shrink );
|
||||||
|
Loading…
Reference in New Issue
Block a user