From 670b93ba742968e9265bf14dada5b8cc10f09c4e Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 21 Oct 2008 17:10:16 +0000 Subject: [PATCH] added yafr --- include/vips/interpolate.h | 77 +- libsrc/mosaicing/gegl-sampler-yafr-smooth.c | 686 ++++++++++++++ libsrc/mosaicing/interpolate.c | 957 +++++++++++++++++--- libsrc/mosaicing/mosaicing_dispatch.c | 16 +- 4 files changed, 1576 insertions(+), 160 deletions(-) create mode 100644 libsrc/mosaicing/gegl-sampler-yafr-smooth.c diff --git a/include/vips/interpolate.h b/include/vips/interpolate.h index c927d872..1e5670c0 100644 --- a/include/vips/interpolate.h +++ b/include/vips/interpolate.h @@ -147,9 +147,6 @@ VipsInterpolate *vips_interpolate_nearest_static( void ); typedef struct _VipsInterpolateBilinear { VipsInterpolate parent_object; - /* Set this to not use tables ...slightly more accurate. - */ - gboolean slow; } VipsInterpolateBilinear; typedef struct _VipsInterpolateBilinearClass { @@ -164,13 +161,51 @@ typedef struct _VipsInterpolateBilinearClass { } VipsInterpolateBilinearClass; GType vips_interpolate_bilinear_get_type( void ); -void vips_interpolate_bilinear_set_slow( VipsInterpolateBilinear *, gboolean ); VipsInterpolate *vips_interpolate_bilinear_new( void ); /* Convenience: return a static fast bilinear, so no need to free it. */ VipsInterpolate *vips_interpolate_bilinear_static( void ); +/* Slow bilinear class starts. + */ + +#define VIPS_TYPE_INTERPOLATE_BILINEAR_SLOW \ + (vips_interpolate_bilinear_slow_get_type()) +#define VIPS_INTERPOLATE_BILINEAR_SLOW( obj ) \ + (G_TYPE_CHECK_INSTANCE_CAST( (obj), \ + VIPS_TYPE_INTERPOLATE_BILINEAR_SLOW, VipsInterpolateBilinearSlow )) +#define VIPS_INTERPOLATE_BILINEAR_SLOW_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_CAST( (klass), \ + VIPS_TYPE_INTERPOLATE_BILINEAR_SLOW, \ + VipsInterpolateBilinearSlowClass)) +#define VIPS_IS_INTERPOLATE_BILINEAR_SLOW( obj ) \ + (G_TYPE_CHECK_INSTANCE_TYPE( (obj), \ + VIPS_TYPE_INTERPOLATE_BILINEAR_SLOW )) +#define VIPS_IS_INTERPOLATE_BILINEAR_SLOW_CLASS( klass ) \ + (G_TYPE_CHECK_CLASS_TYPE( (klass), \ + VIPS_TYPE_INTERPOLATE_BILINEAR_SLOW )) +#define VIPS_INTERPOLATE_BILINEAR_SLOW_GET_CLASS( obj ) \ + (G_TYPE_INSTANCE_GET_CLASS( (obj), \ + VIPS_TYPE_INTERPOLATE_BILINEAR_SLOW, VipsInterpolateBilinearSlowClass )) + +typedef struct _VipsInterpolateBilinearSlow { + VipsInterpolate parent_object; + +} VipsInterpolateBilinearSlow; + +typedef struct _VipsInterpolateBilinearSlowClass { + VipsInterpolateClass parent_class; + +} VipsInterpolateBilinearSlowClass; + +GType vips_interpolate_bilinear_slow_get_type( void ); +VipsInterpolate *vips_interpolate_bilinear_slow_new( void ); + +/* Convenience: return a static fast bilinear_slow, so no need to free it. + */ +VipsInterpolate *vips_interpolate_bilinear_slow_static( void ); + /* Yafr class starts. */ @@ -192,6 +227,36 @@ VipsInterpolate *vips_interpolate_bilinear_static( void ); typedef struct _VipsInterpolateYafr { VipsInterpolate parent_object; + /* "sharpening" is a continuous method parameter which is + * proportional to the amount of "diagonal straightening" which the + * nonlinear correction part of the method may add to the underlying + * linear scheme. You may also think of it as a sharpening + * parameter: higher values correspond to more sharpening, and + * negative values lead to strange looking effects. + * + * The default value is sharpening = 29/32 when the scheme being + * "straightened" is Catmull-Rom---as is the case here. This value + * fixes key pixel values near the diagonal boundary between two + * monochrome regions (the diagonal boundary pixel values being set + * to the halfway colour). + * + * If resampling seems to add unwanted texture artifacts, push + * sharpening toward 0. It is not generally not recommended to set + * sharpening to a value larger than 4. + * + * Sharpening is halved because the .5 which has to do with the + * relative coordinates of the evaluation points (which has to do + * with .5*rite_width etc) is folded into the constant to save + * flops. Consequently, the largest recommended value of + * sharpening_over_two is 2=4/2. + * + * In order to simplify interfacing with users, the parameter which + * should be set by the user is normalized so that user_sharpening = + * 1 when sharpening is equal to the recommended value. Consistently + * with the above discussion, values of user_sharpening between 0 + * and about 3.625 give good results. + */ + double sharpening; } VipsInterpolateYafr; typedef struct _VipsInterpolateYafrClass { @@ -199,8 +264,10 @@ typedef struct _VipsInterpolateYafrClass { } VipsInterpolateYafrClass; +GType vips_interpolate_yafr_get_type( void ); VipsInterpolate *vips_interpolate_yafr_new( void ); -void vips_interpolate_yafr_set_thing( VipsInterpolateYafr *, double thing ); +void vips_interpolate_yafr_set_sharpening( VipsInterpolateYafr *, + double sharpening ); /* Convenience: return a static default yafr, so no need to free it. */ diff --git a/libsrc/mosaicing/gegl-sampler-yafr-smooth.c b/libsrc/mosaicing/gegl-sampler-yafr-smooth.c new file mode 100644 index 00000000..5f4b0b8e --- /dev/null +++ b/libsrc/mosaicing/gegl-sampler-yafr-smooth.c @@ -0,0 +1,686 @@ +/* This file is part of GEGL + * + * GEGL 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 3 of the + * License, or (at your option) any later version. + * + * GEGL 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 GEGL; if not, see + * . + * + * 2008 (c) Nicolas Robidoux (developer of Yet Another Fast + * Resampler). + * + * Acknowledgement: N. Robidoux's research on YAFR funded in part by + * an NSERC (National Science and Engineering Research Council of + * Canada) Discovery Grant. + */ + +#include +#include "gegl-types.h" +#include "gegl-buffer-private.h" +#include "gegl-sampler-yafr.h" + +#include + +#ifndef restrict +#ifdef __restrict +#define restrict __restrict +#else +#ifdef __restrict__ +#define restrict __restrict__ +#else +#define restrict +#endif +#endif +#endif + +#ifndef unlikely +#ifdef __builtin_expect +#define unlikely(x) __builtin_expect((x),0) +#else +#define unlikely(x) (x) +#endif +#endif + +enum +{ + PROP_0, + PROP_LAST +}; + +static void gegl_sampler_yafr_get ( GeglSampler *self, + const gdouble x, + const gdouble y, + void *output); + +static void set_property ( GObject *gobject, + guint property_id, + const GValue *value, + GParamSpec *pspec); + +static void get_property (GObject *gobject, + guint property_id, + GValue *value, + GParamSpec *pspec); + +G_DEFINE_TYPE (GeglSamplerYafr, gegl_sampler_yafr, GEGL_TYPE_SAMPLER) + +/* + * YAFR = Yet Another Fast Resampler + * + * Yet Another Fast Resampler is a nonlinear resampler which consists + * of a linear scheme (in this version, Catmull-Rom) plus a nonlinear + * sharpening correction the purpose of which is the straightening of + * diagonal interfaces between flat colour areas. + * + * Key properties: + * + * YAFR (smooth) is interpolatory: + * + * If asked for the value at the center of an input pixel, it will + * return the corresponding value, unchanged. + * + * YAFR (smooth) preserves local averages: + * + * The average of the reconstructed intensity surface over any region + * is the same as the average of the piecewise constant surface with + * values over pixel areas equal to the input pixel values (the + * "nearest neighbour" surface), except for a small amount of blur at + * the boundary of the region. More precicely: YAFR (smooth) is a box + * filtered exact area method. + * + * Main weaknesses of YAFR (smooth): + * + * Weakness 1: YAFR (smooth) improves on Catmull-Rom only for images + * with at least a little bit of smoothness. + * + * Weakness 2: Catmull-Rom introduces a lot of haloing. YAFR (smooth) + * is based on Catmull-Rom, and consequently it too introduces a lot + * of haloing. + * + * More details regarding Weakness 1: + * + * If a portion of the image is such that every pixel has immediate + * neighbours in the horizontal and vertical directions which have + * exactly the same pixel value, then YAFR (smooth) boils down to + * Catmull-Rom, and the computation of the correction is a waste. + * Extreme case: If all the pixels are either pure black or pure white + * in some region, as in some text images (more generally, if the + * region is "bichromatic"), then the YAFR (smooth) correction is 0 in + * the interior of the bichromatic region. + */ + +static void +gegl_sampler_yafr_class_init (GeglSamplerYafrClass *klass) +{ + GeglSamplerClass *sampler_class = GEGL_SAMPLER_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->set_property = set_property; + object_class->get_property = get_property; + + sampler_class->get = gegl_sampler_yafr_get; + } + +static void +gegl_sampler_yafr_init (GeglSamplerYafr *self) +{ + /* + * The computation stencil is 4x4, and sticks out one column to the + * left and one row above the requested integer position: + */ + GEGL_SAMPLER (self)->context_rect = (GeglRectangle){-1,-1,4,4}; + + GEGL_SAMPLER (self)->interpolate_format = babl_format ("RaGaBaA float"); +} + +static inline gfloat +catrom_yafr (const gfloat cardinal_one, + const gfloat cardinal_two, + const gfloat cardinal_thr, + const gfloat cardinal_fou, + const gfloat cardinal_uno, + const gfloat cardinal_dos, + const gfloat cardinal_tre, + const gfloat cardinal_qua, + const gfloat left_width_times_up__height_times_rite_width, + const gfloat left_width_times_dow_height_times_rite_width, + const gfloat left_width_times_up__height_times_dow_height, + const gfloat rite_width_times_up__height_times_dow_height, + const gfloat* restrict this_channels_uno_one_bptr) +{ + /* + * "sharpening" is a continuous method parameter which is + * proportional to the amount of "diagonal straightening" which the + * nonlinear correction part of the method may add to the underlying + * linear scheme. You may also think of it as a sharpening + * parameter: higher values correspond to more sharpening, and + * negative values lead to strange looking effects. + * + * The default value is sharpening = 29/32 when the scheme being + * "straightened" is Catmull-Rom---as is the case here. This value + * fixes key pixel values near the diagonal boundary between two + * monochrome regions (the diagonal boundary pixel values being set + * to the halfway colour). + * + * If resampling seems to add unwanted texture artifacts, push + * sharpening toward 0. It is not generally not recommended to set + * sharpening to a value larger than 4. + * + * Sharpening is halved because the .5 which has to do with the + * relative coordinates of the evaluation points (which has to do + * with .5*rite_width etc) is folded into the constant to save + * flops. Consequently, the largest recommended value of + * sharpening_over_two is 2=4/2. + * + * In order to simplify interfacing with users, the parameter which + * should be set by the user is normalized so that user_sharpening = + * 1 when sharpening is equal to the recommended value. Consistently + * with the above discussion, values of user_sharpening between 0 + * and about 3.625 give good results. + */ + const gfloat user_sharpening = 1.f; + const gfloat sharpening_over_two = user_sharpening * 0.453125f; + + /* + * The input pixel values are described by the following stencil. + * Spanish abbreviations are used to label positions from top to + * bottom, English ones to label positions from left to right,: + * + * (ix-1,iy-1) (ix,iy-1) (ix+1,iy-1) (ix+2,iy-1) + * =uno_one =uno_two =uno_thr = uno_fou + * + * (ix-1,iy) (ix,iy) (ix+1,iy) (ix+2,iy) + * =dos_one =dos_two =dos_thr = dos_fou + * + * (ix-1,iy+1) (ix,iy+1) (ix+1,iy+1) (ix+2,iy+1) + * =tre_one =tre_two =tre_thr = tre_fou + * + * (ix-1,iy+2) (ix,iy+2) (ix+1,iy+2) (ix+2,iy+2) + * =qua_one =qua_two =qua_thr = qua_fou + */ + + /* + * Load the useful pixel values for the channel under + * consideration. The this_channels_uno_one_bptr pointer is assumed + * to point to uno_one when catrom_yafr is entered. + */ + const gint channels = 4; + const gint pixels_per_buffer_row = 64; + const gfloat uno_one = + this_channels_uno_one_bptr[ 0 ]; + const gfloat uno_two = + this_channels_uno_one_bptr[ channels ]; + const gfloat uno_thr = + this_channels_uno_one_bptr[ 2 * channels ]; + const gfloat uno_fou = + this_channels_uno_one_bptr[ 3 * channels ]; + + const gfloat dos_one = + this_channels_uno_one_bptr[ pixels_per_buffer_row * channels ]; + const gfloat dos_two = + this_channels_uno_one_bptr[ ( 1 + pixels_per_buffer_row ) * channels ]; + const gfloat dos_thr = + this_channels_uno_one_bptr[ ( 2 + pixels_per_buffer_row ) * channels ]; + const gfloat dos_fou = + this_channels_uno_one_bptr[ ( 3 + pixels_per_buffer_row ) * channels ]; + + const gfloat tre_one = + this_channels_uno_one_bptr[ 2 * pixels_per_buffer_row * channels ]; + const gfloat tre_two = + this_channels_uno_one_bptr[ ( 1 + 2 * pixels_per_buffer_row ) * channels ]; + const gfloat tre_thr = + this_channels_uno_one_bptr[ ( 2 + 2 * pixels_per_buffer_row ) * channels ]; + const gfloat tre_fou = + this_channels_uno_one_bptr[ ( 3 + 2 * pixels_per_buffer_row ) * channels ]; + + const gfloat qua_one = + this_channels_uno_one_bptr[ 3 * pixels_per_buffer_row * channels ]; + const gfloat qua_two = + this_channels_uno_one_bptr[ ( 1 + 3 * pixels_per_buffer_row ) * channels ]; + const gfloat qua_thr = + this_channels_uno_one_bptr[ ( 2 + 3 * pixels_per_buffer_row ) * channels ]; + const gfloat qua_fou = + this_channels_uno_one_bptr[ ( 3 + 3 * pixels_per_buffer_row ) * channels ]; + + /* + * Computation of the YAFR correction: + * + * Basically, if two consecutive pixel value differences have the + * same sign, the smallest one (in absolute value) is taken to be + * the corresponding slope. If they don't have the same sign, the + * corresponding slope is set to 0. + * + * Four such pairs (vertical and horizontal) of slopes need to be + * computed, one pair for each of the pixels which potentially + * overlap the unit area centered at the interpolation point. + */ + /* + * Beginning of the computation of the "up" horizontal slopes: + */ + const gfloat prem__up = dos_two - dos_one; + const gfloat deux__up = dos_thr - dos_two; + const gfloat troi__up = dos_fou - dos_thr; + /* + * "down" horizontal slopes: + */ + const gfloat prem_dow = tre_two - tre_one; + const gfloat deux_dow = tre_thr - tre_two; + const gfloat troi_dow = tre_fou - tre_thr; + /* + * "left" vertical slopes: + */ + const gfloat prem_left = dos_two - uno_two; + const gfloat deux_left = tre_two - dos_two; + const gfloat troi_left = qua_two - tre_two; + /* + * "right" vertical slopes: + */ + const gfloat prem_rite = dos_thr - uno_thr; + const gfloat deux_rite = tre_thr - dos_thr; + const gfloat troi_rite = qua_thr - tre_thr; + + /* + * Back to "up": + */ + const gfloat prem__up_squared = prem__up * prem__up; + const gfloat deux__up_squared = deux__up * deux__up; + const gfloat troi__up_squared = troi__up * troi__up; + /* + * Back to "down": + */ + const gfloat prem_dow_squared = prem_dow * prem_dow; + const gfloat deux_dow_squared = deux_dow * deux_dow; + const gfloat troi_dow_squared = troi_dow * troi_dow; + /* + * Back to "left": + */ + const gfloat prem_left_squared = prem_left * prem_left; + const gfloat deux_left_squared = deux_left * deux_left; + const gfloat troi_left_squared = troi_left * troi_left; + /* + * Back to "right": + */ + const gfloat prem_rite_squared = prem_rite * prem_rite; + const gfloat deux_rite_squared = deux_rite * deux_rite; + const gfloat troi_rite_squared = troi_rite * troi_rite; + + /* + * "up": + */ + const gfloat prem__up_times_deux__up = prem__up * deux__up; + const gfloat deux__up_times_troi__up = deux__up * troi__up; + /* + * "down": + */ + const gfloat prem_dow_times_deux_dow = prem_dow * deux_dow; + const gfloat deux_dow_times_troi_dow = deux_dow * troi_dow; + /* + * "left": + */ + const gfloat prem_left_times_deux_left = prem_left * deux_left; + const gfloat deux_left_times_troi_left = deux_left * troi_left; + /* + * "right": + */ + const gfloat prem_rite_times_deux_rite = prem_rite * deux_rite; + const gfloat deux_rite_times_troi_rite = deux_rite * troi_rite; + + /* + * Branching parts of the computation of the YAFR correction (could + * be unbranched using arithmetic branching and C99 math intrinsics, + * although the compiler may be smart enough to remove the branching + * on its own): + */ + /* + * "up": + */ + const gfloat prem__up_vs_deux__up = + prem__up_squared < deux__up_squared ? prem__up : deux__up; + const gfloat deux__up_vs_troi__up = + deux__up_squared < troi__up_squared ? deux__up : troi__up; + /* + * "down": + */ + const gfloat prem_dow_vs_deux_dow = + prem_dow_squared < deux_dow_squared ? prem_dow : deux_dow; + const gfloat deux_dow_vs_troi_dow = + deux_dow_squared < troi_dow_squared ? deux_dow : troi_dow; + /* + * "left": + */ + const gfloat prem_left_vs_deux_left = + prem_left_squared < deux_left_squared ? prem_left : deux_left; + const gfloat deux_left_vs_troi_left = + deux_left_squared < troi_left_squared ? deux_left : troi_left; + /* + * "right": + */ + const gfloat prem_rite_vs_deux_rite = + prem_rite_squared < deux_rite_squared ? prem_rite : deux_rite; + const gfloat deux_rite_vs_troi_rite = + deux_rite_squared < troi_rite_squared ? deux_rite : troi_rite; + /* + * The YAFR correction computation will resume after the computation + * of the Catmull-Rom baseline. + */ + + /* + * Catmull-Rom baseline contribution: + */ + const gfloat catmull_rom = + cardinal_uno * + ( + cardinal_one * uno_one + + + cardinal_two * uno_two + + + cardinal_thr * uno_thr + + + cardinal_fou * uno_fou + ) + + + cardinal_dos * + ( + cardinal_one * dos_one + + + cardinal_two * dos_two + + + cardinal_thr * dos_thr + + + cardinal_fou * dos_fou + ) + + + cardinal_tre * + ( + cardinal_one * tre_one + + + cardinal_two * tre_two + + + cardinal_thr * tre_thr + + + cardinal_fou * tre_fou + ) + + + cardinal_qua * + ( + cardinal_one * qua_one + + + cardinal_two * qua_two + + + cardinal_thr * qua_thr + + + cardinal_fou * qua_fou + ); + + /* + * Computation of the YAFR slopes. + */ + /* + * "up": + */ + const gfloat mx_left__up = + prem__up_times_deux__up < 0.f ? 0.f : prem__up_vs_deux__up; + const gfloat mx_rite__up = + deux__up_times_troi__up < 0.f ? 0.f : deux__up_vs_troi__up; + /* + * "down": + */ + const gfloat mx_left_dow = + prem_dow_times_deux_dow < 0.f ? 0.f : prem_dow_vs_deux_dow; + const gfloat mx_rite_dow = + deux_dow_times_troi_dow < 0.f ? 0.f : deux_dow_vs_troi_dow; + /* + * "left": + */ + const gfloat my_left__up = + prem_left_times_deux_left < 0.f ? 0.f : prem_left_vs_deux_left; + const gfloat my_left_dow = + deux_left_times_troi_left < 0.f ? 0.f : deux_left_vs_troi_left; + /* + * "down": + */ + const gfloat my_rite__up = + prem_rite_times_deux_rite < 0.f ? 0.f : prem_rite_vs_deux_rite; + const gfloat my_rite_dow = + deux_rite_times_troi_rite < 0.f ? 0.f : deux_rite_vs_troi_rite; + + /* + * Assemble the unweighted YAFR correction: + */ + const gfloat unweighted_yafr_correction = + left_width_times_up__height_times_rite_width + * + ( mx_left__up - mx_rite__up ) + + + left_width_times_dow_height_times_rite_width + * + ( mx_left_dow - mx_rite_dow ) + + + left_width_times_up__height_times_dow_height + * + ( my_left__up - my_left_dow ) + + + rite_width_times_up__height_times_dow_height + * + ( my_rite__up - my_rite_dow ); + + /* + * Add the Catmull-Rom baseline and the weighted YAFR correction: + */ + const gfloat newval = + sharpening_over_two * unweighted_yafr_correction + catmull_rom; + + return newval; +} + +static void +gegl_sampler_yafr_get ( GeglSampler *self, + const gdouble x, + const gdouble y, + void *output) +{ + /* + * Note: The computation is structured to foster software + * pipelining. + */ + + /* + * x is understood to increase from left to right, y, from top to + * bottom. Consequently, ix and iy are the indices of the pixel + * located at or to the left, and at or above. the sampling point. + * + * floor is used to make sure that the transition through 0 is + * smooth. If it is known that negative x and y will never be used, + * cast (which truncates) could be used instead. + */ + const gint ix = floorf (x); + const gint iy = floorf (y); + + /* + * Pointer to enlarged input stencil values: + */ + const gfloat* restrict sampler_bptr = gegl_sampler_get_ptr (self, ix, iy); + + /* + * Each (channel's) output pixel value is obtained by combining four + * "pieces," each piece corresponding to the set of points which are + * closest to the four pixels closest to the (x,y) position, pixel + * positions which have coordinates and labels as follows: + * + * (ix,iy) (ix+1,iy) + * =left__up =rite__up + * + * <- (x,y) is somewhere in the convex hull + * + * (ix,iy+1) (ix+1,iy+1) + * =left_dow =rite_dow + */ + /* + * rite_width is the width of the overlaps of the unit averaging box + * (which is centered at the position where an interpolated value is + * desired), with the closest unit pixel areas to the right. + * + * left_width is the width of the overlaps of the unit averaging box + * (which is centered at the position where an interpolated value is + * desired), with the closest unit pixel areas to the left. + */ + const gfloat rite_width = x - ix; + const gfloat dow_height = y - iy; + const gfloat left_width = 1.f - rite_width; + const gfloat up__height = 1.f - dow_height; + /* + * .5*rite_width is the x-coordinate of the center of the overlap of + * the averaging box with the left pixel areas, relative to the + * position of the centers of the left pixels. + * + * -.5*left_width is the x-coordinate ... right pixel areas, + * relative to ... the right pixels. + * + * .5*dow_height is the y-coordinate of the center of the overlap + * of the averaging box with the up pixel areas, relative to the + * position of the centers of the up pixels. + * + * -.5*up__height is the y-coordinate ... down pixel areas, relative + * to ... the down pixels. + */ + const gfloat left_width_times_rite_width = left_width * rite_width; + const gfloat up__height_times_dow_height = up__height * dow_height; + + const gfloat cardinal_two = + left_width_times_rite_width * ( -1.5f * rite_width + 1.f ) + + left_width; + const gfloat cardinal_dos = + up__height_times_dow_height * ( -1.5f * dow_height + 1.f ) + + up__height; + + const gfloat minus_half_left_width_times_rite_width = + -.5f * left_width_times_rite_width; + const gfloat minus_half_up__height_times_dow_height = + -.5f * up__height_times_dow_height; + + const gfloat left_width_times_up__height_times_rite_width = + left_width_times_rite_width * up__height; + const gfloat left_width_times_dow_height_times_rite_width = + left_width_times_rite_width * dow_height; + const gfloat left_width_times_up__height_times_dow_height = + up__height_times_dow_height * left_width; + const gfloat rite_width_times_up__height_times_dow_height = + up__height_times_dow_height * rite_width; + + const gfloat cardinal_one = + minus_half_left_width_times_rite_width * left_width; + const gfloat cardinal_uno = + minus_half_up__height_times_dow_height * up__height; + + const gfloat cardinal_fou = + minus_half_left_width_times_rite_width * rite_width; + const gfloat cardinal_qua = + minus_half_up__height_times_dow_height * dow_height; + + const gfloat cardinal_thr = + 1.f - ( minus_half_left_width_times_rite_width + cardinal_two ); + const gfloat cardinal_tre = + 1.f - ( minus_half_up__height_times_dow_height + cardinal_dos ); + + /* + * The newval array will contain the four (one per channel) + * computed resampled values: + */ + gfloat newval[4]; + + /* + * Set the tile pointer to the first relevant value. Since the + * pointer initially points to dos_two, we need to rewind it one + * tile row, then go back one additional pixel. + */ + const gint channels = 4; + const gint pixels_per_buffer_row = 64; + sampler_bptr -= ( pixels_per_buffer_row + 1 ) * channels; + + newval[0] = catrom_yafr (cardinal_one, + cardinal_two, + cardinal_thr, + cardinal_fou, + cardinal_uno, + cardinal_dos, + cardinal_tre, + cardinal_qua, + left_width_times_up__height_times_rite_width, + left_width_times_dow_height_times_rite_width, + left_width_times_up__height_times_dow_height, + rite_width_times_up__height_times_dow_height, + sampler_bptr++); + newval[1] = catrom_yafr (cardinal_one, + cardinal_two, + cardinal_thr, + cardinal_fou, + cardinal_uno, + cardinal_dos, + cardinal_tre, + cardinal_qua, + left_width_times_up__height_times_rite_width, + left_width_times_dow_height_times_rite_width, + left_width_times_up__height_times_dow_height, + rite_width_times_up__height_times_dow_height, + sampler_bptr++); + newval[2] = catrom_yafr (cardinal_one, + cardinal_two, + cardinal_thr, + cardinal_fou, + cardinal_uno, + cardinal_dos, + cardinal_tre, + cardinal_qua, + left_width_times_up__height_times_rite_width, + left_width_times_dow_height_times_rite_width, + left_width_times_up__height_times_dow_height, + rite_width_times_up__height_times_dow_height, + sampler_bptr++); + newval[3] = catrom_yafr (cardinal_one, + cardinal_two, + cardinal_thr, + cardinal_fou, + cardinal_uno, + cardinal_dos, + cardinal_tre, + cardinal_qua, + left_width_times_up__height_times_rite_width, + left_width_times_dow_height_times_rite_width, + left_width_times_up__height_times_dow_height, + rite_width_times_up__height_times_dow_height, + sampler_bptr); + + /* + * Ship out newval: + */ + babl_process (babl_fish (self->interpolate_format, self->format), + newval, + output, + 1); +} + +static void +set_property ( GObject *gobject, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, property_id, pspec); +} + +static void +get_property (GObject *gobject, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, property_id, pspec); +} diff --git a/libsrc/mosaicing/interpolate.c b/libsrc/mosaicing/interpolate.c index ff267598..c718b631 100644 --- a/libsrc/mosaicing/interpolate.c +++ b/libsrc/mosaicing/interpolate.c @@ -39,6 +39,7 @@ #include #include +#include #include #include @@ -54,6 +55,8 @@ static VipsObjectClass *vips_interpolate_parent_class = NULL; static VipsObjectClass *vips_interpolate_nearest_parent_class = NULL; static VipsObjectClass *vips_interpolate_bilinear_parent_class = NULL; +static VipsObjectClass *vips_interpolate_bilinear_slow_parent_class = NULL; +static VipsObjectClass *vips_interpolate_yafr_parent_class = NULL; #ifdef DEBUG static void @@ -154,18 +157,6 @@ vips_interpolate_get_window_size( VipsInterpolate *interpolate ) /* VipsInterpolateNearest class */ -#ifdef DEBUG -static void -vips_interpolate_nearest_finalize( GObject *gobject ) -{ - printf( "vips_interpolate_nearest_finalize: " ); - vips_object_print( VIPS_OBJECT( gobject ) ); - - G_OBJECT_CLASS( vips_interpolate_nearest_parent_class )-> - finalize( gobject ); -} -#endif /*DEBUG*/ - static void vips_interpolate_nearest_interpolate( VipsInterpolate *interpolate, REGION *out, REGION *in, @@ -197,18 +188,12 @@ vips_interpolate_nearest_interpolate( VipsInterpolate *interpolate, static void vips_interpolate_nearest_class_init( VipsInterpolateNearestClass *class ) { -#ifdef DEBUG - GObjectClass *gobject_class = G_OBJECT_CLASS( class ); -#endif /*DEBUG*/ VipsInterpolateClass *interpolate_class = VIPS_INTERPOLATE_CLASS( class ); vips_interpolate_nearest_parent_class = g_type_class_peek_parent( class ); -#ifdef DEBUG - gobject_class->finalize = vips_interpolate_nearest_finalize; -#endif /*DEBUG*/ interpolate_class->interpolate = vips_interpolate_nearest_interpolate; interpolate_class->window_size = 1; } @@ -220,6 +205,7 @@ vips_interpolate_nearest_init( VipsInterpolateNearest *nearest ) printf( "vips_interpolate_nearest_init: " ); vips_object_print( VIPS_OBJECT( nearest ) ); #endif /*DEBUG*/ + } GType @@ -275,18 +261,6 @@ vips_interpolate_nearest_static( void ) * p3 p4 */ -#ifdef DEBUG -static void -vips_interpolate_bilinear_finalize( GObject *gobject ) -{ - printf( "vips_interpolate_bilinear_finalize: " ); - vips_object_print( VIPS_OBJECT( gobject ) ); - - G_OBJECT_CLASS( vips_interpolate_bilinear_parent_class )-> - finalize( gobject ); -} -#endif /*DEBUG*/ - /* Interpolate a section ... int8/16 types. */ #define BILINEAR_INT( TYPE ) { \ @@ -337,21 +311,6 @@ vips_interpolate_bilinear_finalize( GObject *gobject ) c3 * tp3[z] + c4 * tp4[z]; \ } -/* Interpolate a pel ... don't use the pre-calcuated matricies. - */ -#define BILINEAR_SLOW( TYPE ) { \ - TYPE *tq = (TYPE *) q; \ - \ - const TYPE *tp1 = (TYPE *) p1; \ - const TYPE *tp2 = (TYPE *) p2; \ - const TYPE *tp3 = (TYPE *) p3; \ - const TYPE *tp4 = (TYPE *) p4; \ - \ - for( z = 0; z < b; z++ ) \ - tq[z] = c1 * tp1[z] + c2 * tp2[z] + \ - c3 * tp3[z] + c4 * tp4[z]; \ -} - /* Expand for band types. with a fixed-point interpolator and a float * interpolator. */ @@ -385,109 +344,42 @@ vips_interpolate_bilinear_interpolate( VipsInterpolate *interpolate, const int ps = IM_IMAGE_SIZEOF_PEL( in->im ); const int ls = IM_REGION_LSKIP( in ); const int b = in->im->Bands; - int z; + + /* Subtract 0.5 to centre the bilinear. + */ + const double cx = in_x - 0.5; + const double cy = in_y - 0.5; + + /* Now go to scaled int. + */ + const double sx = cx * VIPS_TRANSFORM_SCALE; + const double sy = cy * VIPS_TRANSFORM_SCALE; + const int sxi = FLOOR( sx ); + const int syi = FLOOR( sy ); + + /* Get index into interpolation table and unscaled integer + * position. + */ + const int xi = sxi & (VIPS_TRANSFORM_SCALE - 1); + const int yi = syi & (VIPS_TRANSFORM_SCALE - 1); + const int in_x_int = sxi >> VIPS_TRANSFORM_SHIFT; + const int in_y_int = syi >> VIPS_TRANSFORM_SHIFT; + + const PEL *p1 = (PEL *) IM_REGION_ADDR( in, in_x_int, in_y_int ); + const PEL *p2 = p1 + ps; + const PEL *p3 = p1 + ls; + const PEL *p4 = p3 + ps; PEL *q = (PEL *) IM_REGION_ADDR( out, out_x, out_y ); + int z; - if( bilinear->slow ) { - /* Subtract 0.5 to centre the bilinear. - */ - const double cx = in_x - 0.5; - const double cy = in_y - 0.5; - - /* Top left corner we interpolate from. - */ - const int xi = FLOOR( cx ); - const int yi = FLOOR( cy ); - - /* Fractional part. - */ - const double X = cx - xi; - const double Y = cy - yi; - - /* Residual. - */ - const double Xd = 1.0 - X; - const double Yd = 1.0 - Y; - - /* Weights. - */ - const double c1 = Xd * Yd; - const double c2 = X * Yd; - const double c3 = Xd * Y; - const double c4 = X * Y; - - const PEL *p1 = (PEL *) IM_REGION_ADDR( in, xi, yi ); - const PEL *p2 = p1 + ps; - const PEL *p3 = p1 + ls; - const PEL *p4 = p3 + ps; - - SWITCH_INTERPOLATE( in->im->BandFmt, - BILINEAR_SLOW, BILINEAR_SLOW ); - } - else { - /* Subtract 0.5 to centre the bilinear. - */ - const double cx = in_x - 0.5; - const double cy = in_y - 0.5; - - /* Now go to scaled int. - */ - const double sx = cx * VIPS_TRANSFORM_SCALE; - const double sy = cy * VIPS_TRANSFORM_SCALE; - const int sxi = FLOOR( sx ); - const int syi = FLOOR( sy ); - - /* Get index into interpolation table and unscaled integer - * position. - */ - const int xi = sxi & (VIPS_TRANSFORM_SCALE - 1); - const int yi = syi & (VIPS_TRANSFORM_SCALE - 1); - const int in_x_int = sxi >> VIPS_TRANSFORM_SHIFT; - const int in_y_int = syi >> VIPS_TRANSFORM_SHIFT; - - const PEL *p1 = (PEL *) - IM_REGION_ADDR( in, in_x_int, in_y_int ); - const PEL *p2 = p1 + ps; - const PEL *p3 = p1 + ls; - const PEL *p4 = p3 + ps; - -{ - unsigned char *tq = (unsigned char *) q; - - const int m1 = class->matrix_int[xi][0]; - const int m2 = class->matrix_int[xi][1]; - const int m3 = class->matrix_int[yi][0]; - const int m4 = class->matrix_int[yi][1]; - - const int c1 = (m3 * m1) >> VIPS_INTERPOLATE_SHIFT; - const int c2 = (m3 * m2) >> VIPS_INTERPOLATE_SHIFT; - const int c3 = (m4 * m1) >> VIPS_INTERPOLATE_SHIFT; - const int c4 = (m4 * m2) >> VIPS_INTERPOLATE_SHIFT; - - const unsigned char *tp1 = (unsigned char *) p1; - const unsigned char *tp2 = (unsigned char *) p2; - const unsigned char *tp3 = (unsigned char *) p3; - const unsigned char *tp4 = (unsigned char *) p4; - - for( z = 0; z < b; z++ ) - tq[z] = (c1 * tp1[z] + c2 * tp2[z] + - c3 * tp3[z] + c4 * tp4[z]) >> VIPS_INTERPOLATE_SHIFT; -} - - /* - SWITCH_INTERPOLATE( in->im->BandFmt, - BILINEAR_INT, BILINEAR_FLOAT ); - */ - } + SWITCH_INTERPOLATE( in->im->BandFmt, + BILINEAR_INT, BILINEAR_FLOAT ); } static void vips_interpolate_bilinear_class_init( VipsInterpolateBilinearClass *class ) { -#ifdef DEBUG - GObjectClass *gobject_class = G_OBJECT_CLASS( class ); -#endif /*DEBUG*/ VipsInterpolateClass *interpolate_class = (VipsInterpolateClass *) class; int x; @@ -495,9 +387,6 @@ vips_interpolate_bilinear_class_init( VipsInterpolateBilinearClass *class ) vips_interpolate_bilinear_parent_class = g_type_class_peek_parent( class ); -#ifdef DEBUG - gobject_class->finalize = vips_interpolate_bilinear_finalize; -#endif /*DEBUG*/ interpolate_class->interpolate = vips_interpolate_bilinear_interpolate; interpolate_class->window_size = 2; @@ -525,7 +414,6 @@ vips_interpolate_bilinear_init( VipsInterpolateBilinear *bilinear ) vips_object_print( VIPS_OBJECT( bilinear ) ); #endif /*DEBUG*/ - bilinear->slow = FALSE; } GType @@ -553,13 +441,6 @@ vips_interpolate_bilinear_get_type( void ) return( type ); } -void -vips_interpolate_bilinear_set_slow( VipsInterpolateBilinear *bilinear, - gboolean slow ) -{ - bilinear->slow = slow; -} - VipsInterpolate * vips_interpolate_bilinear_new( void ) { @@ -580,3 +461,777 @@ vips_interpolate_bilinear_static( void ) return( interpolate ); } +/* VipsInterpolateBilinearSlow class + */ + +/* Slow mode is really just for testing ... it doesn't use the pre-calculated + * interpolation factors or the fixed-point arithmetic. + */ + +/* in this class, name vars in the 2x2 grid as eg. + * p1 p2 + * p3 p4 + */ + +/* Interpolate a pel ... don't use the pre-calcuated matricies. + */ +#define BILINEAR_SLOW( TYPE ) { \ + TYPE *tq = (TYPE *) q; \ + \ + const TYPE *tp1 = (TYPE *) p1; \ + const TYPE *tp2 = (TYPE *) p2; \ + const TYPE *tp3 = (TYPE *) p3; \ + const TYPE *tp4 = (TYPE *) p4; \ + \ + for( z = 0; z < b; z++ ) \ + tq[z] = c1 * tp1[z] + c2 * tp2[z] + \ + c3 * tp3[z] + c4 * tp4[z]; \ +} + +static void +vips_interpolate_bilinear_slow_interpolate( VipsInterpolate *interpolate, + REGION *out, REGION *in, + int out_x, int out_y, double in_x, double in_y ) +{ + VipsInterpolateBilinearSlow *bilinear_slow = + VIPS_INTERPOLATE_BILINEAR_SLOW( interpolate ); + VipsInterpolateBilinearSlowClass *class = + VIPS_INTERPOLATE_BILINEAR_SLOW_GET_CLASS( interpolate ); + + /* Pel size and line size. + */ + const int ps = IM_IMAGE_SIZEOF_PEL( in->im ); + const int ls = IM_REGION_LSKIP( in ); + const int b = in->im->Bands; + + /* Subtract 0.5 to centre the bilinear. + */ + const double cx = in_x - 0.5; + const double cy = in_y - 0.5; + + /* Top left corner we interpolate from. + */ + const int xi = FLOOR( cx ); + const int yi = FLOOR( cy ); + + /* Fractional part. + */ + const double X = cx - xi; + const double Y = cy - yi; + + /* Residual. + */ + const double Xd = 1.0 - X; + const double Yd = 1.0 - Y; + + /* Weights. + */ + const double c1 = Xd * Yd; + const double c2 = X * Yd; + const double c3 = Xd * Y; + const double c4 = X * Y; + + const PEL *p1 = (PEL *) IM_REGION_ADDR( in, xi, yi ); + const PEL *p2 = p1 + ps; + const PEL *p3 = p1 + ls; + const PEL *p4 = p3 + ps; + + PEL *q = (PEL *) IM_REGION_ADDR( out, out_x, out_y ); + int z; + + SWITCH_INTERPOLATE( in->im->BandFmt, + BILINEAR_SLOW, BILINEAR_SLOW ); +} + +static void +vips_interpolate_bilinear_slow_class_init( VipsInterpolateBilinearSlowClass *class ) +{ + VipsInterpolateClass *interpolate_class = + (VipsInterpolateClass *) class; + + vips_interpolate_bilinear_slow_parent_class = + g_type_class_peek_parent( class ); + + interpolate_class->interpolate = + vips_interpolate_bilinear_slow_interpolate; + interpolate_class->window_size = 2; +} + +static void +vips_interpolate_bilinear_slow_init( + VipsInterpolateBilinearSlow *bilinear_slow ) +{ +#ifdef DEBUG + printf( "vips_interpolate_bilinear_slow_init: " ); + vips_object_print( VIPS_OBJECT( bilinear_slow ) ); +#endif /*DEBUG*/ + +} + +GType +vips_interpolate_bilinear_slow_get_type( void ) +{ + static GType type = 0; + + if( !type ) { + static const GTypeInfo info = { + sizeof( VipsInterpolateBilinearSlowClass ), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) + vips_interpolate_bilinear_slow_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof( VipsInterpolateBilinearSlow ), + 32, /* n_preallocs */ + (GInstanceInitFunc) vips_interpolate_bilinear_slow_init, + }; + + type = g_type_register_static( VIPS_TYPE_INTERPOLATE, + "VipsInterpolateBilinearSlow", &info, 0 ); + } + + return( type ); +} + +VipsInterpolate * +vips_interpolate_bilinear_slow_new( void ) +{ + return( VIPS_INTERPOLATE( g_object_new( + VIPS_TYPE_INTERPOLATE_BILINEAR_SLOW, NULL ) ) ); +} + +/* Convenience: return a static bilinear_slow you don't need to free. + */ +VipsInterpolate * +vips_interpolate_bilinear_slow_static( void ) +{ + static VipsInterpolate *interpolate = NULL; + + if( !interpolate ) + interpolate = vips_interpolate_bilinear_slow_new(); + + return( interpolate ); +} + +/* VipsInterpolateYafr class + */ + +/* Copy-paste of gegl-sampler-yafr-smooth.c starts + */ + +#ifndef restrict +#ifdef __restrict +#define restrict __restrict +#else +#ifdef __restrict__ +#define restrict __restrict__ +#else +#define restrict +#endif +#endif +#endif + +#ifndef unlikely +#ifdef __builtin_expect +#define unlikely(x) __builtin_expect((x),0) +#else +#define unlikely(x) (x) +#endif +#endif + +/* + * YAFR = Yet Another Fast Resampler + * + * Yet Another Fast Resampler is a nonlinear resampler which consists + * of a linear scheme (in this version, Catmull-Rom) plus a nonlinear + * sharpening correction the purpose of which is the straightening of + * diagonal interfaces between flat colour areas. + * + * Key properties: + * + * YAFR (smooth) is interpolatory: + * + * If asked for the value at the center of an input pixel, it will + * return the corresponding value, unchanged. + * + * YAFR (smooth) preserves local averages: + * + * The average of the reconstructed intensity surface over any region + * is the same as the average of the piecewise constant surface with + * values over pixel areas equal to the input pixel values (the + * "nearest neighbour" surface), except for a small amount of blur at + * the boundary of the region. More precicely: YAFR (smooth) is a box + * filtered exact area method. + * + * Main weaknesses of YAFR (smooth): + * + * Weakness 1: YAFR (smooth) improves on Catmull-Rom only for images + * with at least a little bit of smoothness. + * + * Weakness 2: Catmull-Rom introduces a lot of haloing. YAFR (smooth) + * is based on Catmull-Rom, and consequently it too introduces a lot + * of haloing. + * + * More details regarding Weakness 1: + * + * If a portion of the image is such that every pixel has immediate + * neighbours in the horizontal and vertical directions which have + * exactly the same pixel value, then YAFR (smooth) boils down to + * Catmull-Rom, and the computation of the correction is a waste. + * Extreme case: If all the pixels are either pure black or pure white + * in some region, as in some text images (more generally, if the + * region is "bichromatic"), then the YAFR (smooth) correction is 0 in + * the interior of the bichromatic region. + */ + +/* Pointers to write to / read from, how much to add to move right a pixel, + * how much to add to move down a line. + */ + +static inline void +catrom_yafr (float* restrict out, const float* restrict in, + const int channels, + const int pixels_per_buffer_row, + const float sharpening, + + const float cardinal_one, + const float cardinal_two, + const float cardinal_thr, + const float cardinal_fou, + const float cardinal_uno, + const float cardinal_dos, + const float cardinal_tre, + const float cardinal_qua, + const float left_width_times_up__height_times_rite_width, + const float left_width_times_dow_height_times_rite_width, + const float left_width_times_up__height_times_dow_height, + const float rite_width_times_up__height_times_dow_height) +{ + + /* "sharpening" is a continuous method parameter which is + * proportional to the amount of "diagonal straightening" which the + * nonlinear correction part of the method may add to the underlying + * linear scheme. You may also think of it as a sharpening + * parameter: higher values correspond to more sharpening, and + * negative values lead to strange looking effects. + * + * The default value is sharpening = 29/32 when the scheme being + * "straightened" is Catmull-Rom---as is the case here. This value + * fixes key pixel values near the diagonal boundary between two + * monochrome regions (the diagonal boundary pixel values being set + * to the halfway colour). + * + * If resampling seems to add unwanted texture artifacts, push + * sharpening toward 0. It is not generally not recommended to set + * sharpening to a value larger than 4. + * + * Sharpening is halved because the .5 which has to do with the + * relative coordinates of the evaluation points (which has to do + * with .5*rite_width etc) is folded into the constant to save + * flops. Consequently, the largest recommended value of + * sharpening_over_two is 2=4/2. + * + * In order to simplify interfacing with users, the parameter which + * should be set by the user is normalized so that user_sharpening = + * 1 when sharpening is equal to the recommended value. Consistently + * with the above discussion, values of user_sharpening between 0 + * and about 3.625 give good results. + */ + + const float sharpening_over_two = sharpening * 0.453125f; + + /* + * The input pixel values are described by the following stencil. + * Spanish abbreviations are used to label positions from top to + * bottom, English ones to label positions from left to right,: + * + * (ix-1,iy-1) (ix,iy-1) (ix+1,iy-1) (ix+2,iy-1) + * =uno_one =uno_two =uno_thr = uno_fou + * + * (ix-1,iy) (ix,iy) (ix+1,iy) (ix+2,iy) + * =dos_one =dos_two =dos_thr = dos_fou + * + * (ix-1,iy+1) (ix,iy+1) (ix+1,iy+1) (ix+2,iy+1) + * =tre_one =tre_two =tre_thr = tre_fou + * + * (ix-1,iy+2) (ix,iy+2) (ix+1,iy+2) (ix+2,iy+2) + * =qua_one =qua_two =qua_thr = qua_fou + */ + + /* + * Load the useful pixel values for the channel under + * consideration. The in pointer is assumed + * to point to uno_one when catrom_yafr is entered. + */ + const float uno_one = in[ 0 ]; + const float uno_two = in[ channels ]; + const float uno_thr = in[ 2 * channels ]; + const float uno_fou = in[ 3 * channels ]; + + const float dos_one = in[ pixels_per_buffer_row * channels ]; + const float dos_two = in[ ( 1 + pixels_per_buffer_row ) * channels ]; + const float dos_thr = in[ ( 2 + pixels_per_buffer_row ) * channels ]; + const float dos_fou = in[ ( 3 + pixels_per_buffer_row ) * channels ]; + + const float tre_one = in[ 2 * pixels_per_buffer_row * channels ]; + const float tre_two = in[ ( 1 + 2 * pixels_per_buffer_row ) * channels ]; + const float tre_thr = in[ ( 2 + 2 * pixels_per_buffer_row ) * channels ]; + const float tre_fou = in[ ( 3 + 2 * pixels_per_buffer_row ) * channels ]; + + const float qua_one = in[ 3 * pixels_per_buffer_row * channels ]; + const float qua_two = in[ ( 1 + 3 * pixels_per_buffer_row ) * channels ]; + const float qua_thr = in[ ( 2 + 3 * pixels_per_buffer_row ) * channels ]; + const float qua_fou = in[ ( 3 + 3 * pixels_per_buffer_row ) * channels ]; + + /* + * Computation of the YAFR correction: + * + * Basically, if two consecutive pixel value differences have the + * same sign, the smallest one (in absolute value) is taken to be + * the corresponding slope. If they don't have the same sign, the + * corresponding slope is set to 0. + * + * Four such pairs (vertical and horizontal) of slopes need to be + * computed, one pair for each of the pixels which potentially + * overlap the unit area centered at the interpolation point. + */ + /* + * Beginning of the computation of the "up" horizontal slopes: + */ + const float prem__up = dos_two - dos_one; + const float deux__up = dos_thr - dos_two; + const float troi__up = dos_fou - dos_thr; + /* + * "down" horizontal slopes: + */ + const float prem_dow = tre_two - tre_one; + const float deux_dow = tre_thr - tre_two; + const float troi_dow = tre_fou - tre_thr; + /* + * "left" vertical slopes: + */ + const float prem_left = dos_two - uno_two; + const float deux_left = tre_two - dos_two; + const float troi_left = qua_two - tre_two; + /* + * "right" vertical slopes: + */ + const float prem_rite = dos_thr - uno_thr; + const float deux_rite = tre_thr - dos_thr; + const float troi_rite = qua_thr - tre_thr; + + /* + * Back to "up": + */ + const float prem__up_squared = prem__up * prem__up; + const float deux__up_squared = deux__up * deux__up; + const float troi__up_squared = troi__up * troi__up; + /* + * Back to "down": + */ + const float prem_dow_squared = prem_dow * prem_dow; + const float deux_dow_squared = deux_dow * deux_dow; + const float troi_dow_squared = troi_dow * troi_dow; + /* + * Back to "left": + */ + const float prem_left_squared = prem_left * prem_left; + const float deux_left_squared = deux_left * deux_left; + const float troi_left_squared = troi_left * troi_left; + /* + * Back to "right": + */ + const float prem_rite_squared = prem_rite * prem_rite; + const float deux_rite_squared = deux_rite * deux_rite; + const float troi_rite_squared = troi_rite * troi_rite; + + /* + * "up": + */ + const float prem__up_times_deux__up = prem__up * deux__up; + const float deux__up_times_troi__up = deux__up * troi__up; + /* + * "down": + */ + const float prem_dow_times_deux_dow = prem_dow * deux_dow; + const float deux_dow_times_troi_dow = deux_dow * troi_dow; + /* + * "left": + */ + const float prem_left_times_deux_left = prem_left * deux_left; + const float deux_left_times_troi_left = deux_left * troi_left; + /* + * "right": + */ + const float prem_rite_times_deux_rite = prem_rite * deux_rite; + const float deux_rite_times_troi_rite = deux_rite * troi_rite; + + /* + * Branching parts of the computation of the YAFR correction (could + * be unbranched using arithmetic branching and C99 math intrinsics, + * although the compiler may be smart enough to remove the branching + * on its own): + */ + /* + * "up": + */ + const float prem__up_vs_deux__up = + prem__up_squared < deux__up_squared ? prem__up : deux__up; + const float deux__up_vs_troi__up = + deux__up_squared < troi__up_squared ? deux__up : troi__up; + /* + * "down": + */ + const float prem_dow_vs_deux_dow = + prem_dow_squared < deux_dow_squared ? prem_dow : deux_dow; + const float deux_dow_vs_troi_dow = + deux_dow_squared < troi_dow_squared ? deux_dow : troi_dow; + /* + * "left": + */ + const float prem_left_vs_deux_left = + prem_left_squared < deux_left_squared ? prem_left : deux_left; + const float deux_left_vs_troi_left = + deux_left_squared < troi_left_squared ? deux_left : troi_left; + /* + * "right": + */ + const float prem_rite_vs_deux_rite = + prem_rite_squared < deux_rite_squared ? prem_rite : deux_rite; + const float deux_rite_vs_troi_rite = + deux_rite_squared < troi_rite_squared ? deux_rite : troi_rite; + /* + * The YAFR correction computation will resume after the computation + * of the Catmull-Rom baseline. + */ + + /* + * Catmull-Rom baseline contribution: + */ + const float catmull_rom = + cardinal_uno * + ( + cardinal_one * uno_one + + + cardinal_two * uno_two + + + cardinal_thr * uno_thr + + + cardinal_fou * uno_fou + ) + + + cardinal_dos * + ( + cardinal_one * dos_one + + + cardinal_two * dos_two + + + cardinal_thr * dos_thr + + + cardinal_fou * dos_fou + ) + + + cardinal_tre * + ( + cardinal_one * tre_one + + + cardinal_two * tre_two + + + cardinal_thr * tre_thr + + + cardinal_fou * tre_fou + ) + + + cardinal_qua * + ( + cardinal_one * qua_one + + + cardinal_two * qua_two + + + cardinal_thr * qua_thr + + + cardinal_fou * qua_fou + ); + + /* + * Computation of the YAFR slopes. + */ + /* + * "up": + */ + const float mx_left__up = + prem__up_times_deux__up < 0.f ? 0.f : prem__up_vs_deux__up; + const float mx_rite__up = + deux__up_times_troi__up < 0.f ? 0.f : deux__up_vs_troi__up; + /* + * "down": + */ + const float mx_left_dow = + prem_dow_times_deux_dow < 0.f ? 0.f : prem_dow_vs_deux_dow; + const float mx_rite_dow = + deux_dow_times_troi_dow < 0.f ? 0.f : deux_dow_vs_troi_dow; + /* + * "left": + */ + const float my_left__up = + prem_left_times_deux_left < 0.f ? 0.f : prem_left_vs_deux_left; + const float my_left_dow = + deux_left_times_troi_left < 0.f ? 0.f : deux_left_vs_troi_left; + /* + * "right": + */ + const float my_rite__up = + prem_rite_times_deux_rite < 0.f ? 0.f : prem_rite_vs_deux_rite; + const float my_rite_dow = + deux_rite_times_troi_rite < 0.f ? 0.f : deux_rite_vs_troi_rite; + + /* + * Assemble the unweighted YAFR correction: + */ + const float unweighted_yafr_correction = + left_width_times_up__height_times_rite_width + * + ( mx_left__up - mx_rite__up ) + + + left_width_times_dow_height_times_rite_width + * + ( mx_left_dow - mx_rite_dow ) + + + left_width_times_up__height_times_dow_height + * + ( my_left__up - my_left_dow ) + + + rite_width_times_up__height_times_dow_height + * + ( my_rite__up - my_rite_dow ); + + /* + * Add the Catmull-Rom baseline and the weighted YAFR correction: + */ + const float newval = + sharpening_over_two * unweighted_yafr_correction + catmull_rom; + + *out = newval; +} + +static void +vips_interpolate_yafr_interpolate( VipsInterpolate *interpolate, + REGION *out, REGION *in, + int out_x, int out_y, double x, double y ) +{ + VipsInterpolateYafr *yafr = VIPS_INTERPOLATE_YAFR( interpolate ); + VipsInterpolateYafrClass *class = + VIPS_INTERPOLATE_YAFR_GET_CLASS( interpolate ); + + /* + * Note: The computation is structured to foster software + * pipelining. + */ + + /* + * x is understood to increase from left to right, y, from top to + * bottom. Consequently, ix and iy are the indices of the pixel + * located at or to the left, and at or above. the sampling point. + * + * floor is used to make sure that the transition through 0 is + * smooth. If it is known that negative x and y will never be used, + * cast (which truncates) could be used instead. + */ + const gint ix = FLOOR (x); + const gint iy = FLOOR (y); + + /* + * Each (channel's) output pixel value is obtained by combining four + * "pieces," each piece corresponding to the set of points which are + * closest to the four pixels closest to the (x,y) position, pixel + * positions which have coordinates and labels as follows: + * + * (ix,iy) (ix+1,iy) + * =left__up =rite__up + * + * <- (x,y) is somewhere in the convex hull + * + * (ix,iy+1) (ix+1,iy+1) + * =left_dow =rite_dow + */ + /* + * rite_width is the width of the overlaps of the unit averaging box + * (which is centered at the position where an interpolated value is + * desired), with the closest unit pixel areas to the right. + * + * left_width is the width of the overlaps of the unit averaging box + * (which is centered at the position where an interpolated value is + * desired), with the closest unit pixel areas to the left. + */ + const float rite_width = x - ix; + const float dow_height = y - iy; + const float left_width = 1.f - rite_width; + const float up__height = 1.f - dow_height; + /* + * .5*rite_width is the x-coordinate of the center of the overlap of + * the averaging box with the left pixel areas, relative to the + * position of the centers of the left pixels. + * + * -.5*left_width is the x-coordinate ... right pixel areas, + * relative to ... the right pixels. + * + * .5*dow_height is the y-coordinate of the center of the overlap + * of the averaging box with the up pixel areas, relative to the + * position of the centers of the up pixels. + * + * -.5*up__height is the y-coordinate ... down pixel areas, relative + * to ... the down pixels. + */ + const float left_width_times_rite_width = left_width * rite_width; + const float up__height_times_dow_height = up__height * dow_height; + + const float cardinal_two = + left_width_times_rite_width * ( -1.5f * rite_width + 1.f ) + + left_width; + const float cardinal_dos = + up__height_times_dow_height * ( -1.5f * dow_height + 1.f ) + + up__height; + + const float minus_half_left_width_times_rite_width = + -.5f * left_width_times_rite_width; + const float minus_half_up__height_times_dow_height = + -.5f * up__height_times_dow_height; + + const float left_width_times_up__height_times_rite_width = + left_width_times_rite_width * up__height; + const float left_width_times_dow_height_times_rite_width = + left_width_times_rite_width * dow_height; + const float left_width_times_up__height_times_dow_height = + up__height_times_dow_height * left_width; + const float rite_width_times_up__height_times_dow_height = + up__height_times_dow_height * rite_width; + + const float cardinal_one = + minus_half_left_width_times_rite_width * left_width; + const float cardinal_uno = + minus_half_up__height_times_dow_height * up__height; + + const float cardinal_fou = + minus_half_left_width_times_rite_width * rite_width; + const float cardinal_qua = + minus_half_up__height_times_dow_height * dow_height; + + const float cardinal_thr = + 1.f - ( minus_half_left_width_times_rite_width + cardinal_two ); + const float cardinal_tre = + 1.f - ( minus_half_up__height_times_dow_height + cardinal_dos ); + + /* + * Set the tile pointer to the first relevant value. Since the + * pointer initially points to dos_two, we need to rewind it one + * tile row, then go back one additional pixel. + */ + const PEL *p = (PEL *) IM_REGION_ADDR( in, ix - 1, iy - 1 ); + + /* Pel size and line size. + */ + const int channels = in->im->Bands; + const int pixels_per_buffer_row = + IM_REGION_LSKIP( in ) / sizeof( float ); + + /* Where we write the result. + */ + PEL *q = (PEL *) IM_REGION_ADDR( out, out_x, out_y ); + int z; + + for( z = 0; z < channels; z++ ) + catrom_yafr ((float *) q + z, (float *) p + z, + channels, pixels_per_buffer_row, + yafr->sharpening, + cardinal_one, + cardinal_two, + cardinal_thr, + cardinal_fou, + cardinal_uno, + cardinal_dos, + cardinal_tre, + cardinal_qua, + left_width_times_up__height_times_rite_width, + left_width_times_dow_height_times_rite_width, + left_width_times_up__height_times_dow_height, + rite_width_times_up__height_times_dow_height); +} + +static void +vips_interpolate_yafr_class_init( VipsInterpolateYafrClass *class ) +{ + VipsInterpolateClass *interpolate_class = + VIPS_INTERPOLATE_CLASS( class ); + + vips_interpolate_yafr_parent_class = + g_type_class_peek_parent( class ); + + interpolate_class->interpolate = vips_interpolate_yafr_interpolate; + interpolate_class->window_size = 4; +} + +static void +vips_interpolate_yafr_init( VipsInterpolateYafr *yafr ) +{ +#ifdef DEBUG + printf( "vips_interpolate_yafr_init: " ); + vips_object_print( VIPS_OBJECT( yafr ) ); +#endif /*DEBUG*/ + + yafr->sharpening = 1.0; +} + +GType +vips_interpolate_yafr_get_type( void ) +{ + static GType type = 0; + + if( !type ) { + static const GTypeInfo info = { + sizeof( VipsInterpolateYafrClass ), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) vips_interpolate_yafr_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof( VipsInterpolateYafr ), + 32, /* n_preallocs */ + (GInstanceInitFunc) vips_interpolate_yafr_init, + }; + + type = g_type_register_static( VIPS_TYPE_INTERPOLATE, + "VipsInterpolateYafr", &info, 0 ); + } + + return( type ); +} + +VipsInterpolate * +vips_interpolate_yafr_new( void ) +{ + return( VIPS_INTERPOLATE( g_object_new( + VIPS_TYPE_INTERPOLATE_YAFR, NULL ) ) ); +} + +void +vips_interpolate_yafr_set_sharpening( VipsInterpolateYafr *yafr, + double sharpening ) +{ + yafr->sharpening = sharpening; +} + +/* Convenience: return a static yafr you don't need to free. + */ +VipsInterpolate * +vips_interpolate_yafr_static( void ) +{ + static VipsInterpolate *interpolate = NULL; + + if( !interpolate ) + interpolate = vips_interpolate_yafr_new(); + + return( interpolate ); +} + + diff --git a/libsrc/mosaicing/mosaicing_dispatch.c b/libsrc/mosaicing/mosaicing_dispatch.c index 1c64f769..cdd7b944 100644 --- a/libsrc/mosaicing/mosaicing_dispatch.c +++ b/libsrc/mosaicing/mosaicing_dispatch.c @@ -565,13 +565,21 @@ affinei_vec( im_object *argv ) break; case 3: - interpolate = vips_interpolate_bilinear_new(); - vips_interpolate_bilinear_set_slow( - VIPS_INTERPOLATE_BILINEAR( interpolate ), TRUE ); + interpolate = vips_interpolate_bilinear_slow_new(); + break; + + case 4: + interpolate = vips_interpolate_yafr_new(); + break; + + case 5: + interpolate = vips_interpolate_yafr_new(); + vips_interpolate_yafr_set_sharpening( + VIPS_INTERPOLATE_YAFR( interpolate ), 2.0 ); break; default: - im_error( "affinei_vec", _( "b ad interpolation" ) ); + im_error( "affinei_vec", _( "bad interpolation" ) ); return( -1 ); }