blob: 636f074971ee37270c94ffa35bf34c977b0187d9 [file] [log] [blame]
/* bicubic (catmull-rom) interpolator
*/
/*
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk
*/
/* Bicubic (Catmull-Rom) interpolator derived from Nicolas Robidoux's
* original YAFR resampler with permission and thanks.
*/
/*
#define DEBUG
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif /*HAVE_CONFIG_H*/
#include <vips/intl.h>
#include <stdio.h>
#include <stdlib.h>
#include <vips/vips.h>
#include <vips/internal.h>
#include "templates.h"
#ifdef WITH_DMALLOC
#include <dmalloc.h>
#endif /*WITH_DMALLOC*/
#define VIPS_TYPE_INTERPOLATE_BICUBIC \
(vips_interpolate_bicubic_get_type())
#define VIPS_INTERPOLATE_BICUBIC( obj ) \
(G_TYPE_CHECK_INSTANCE_CAST( (obj), \
VIPS_TYPE_INTERPOLATE_BICUBIC, VipsInterpolateBicubic ))
#define VIPS_INTERPOLATE_BICUBIC_CLASS( klass ) \
(G_TYPE_CHECK_CLASS_CAST( (klass), \
VIPS_TYPE_INTERPOLATE_BICUBIC, VipsInterpolateBicubicClass))
#define VIPS_IS_INTERPOLATE_BICUBIC( obj ) \
(G_TYPE_CHECK_INSTANCE_TYPE( (obj), VIPS_TYPE_INTERPOLATE_BICUBIC ))
#define VIPS_IS_INTERPOLATE_BICUBIC_CLASS( klass ) \
(G_TYPE_CHECK_CLASS_TYPE( (klass), VIPS_TYPE_INTERPOLATE_BICUBIC ))
#define VIPS_INTERPOLATE_BICUBIC_GET_CLASS( obj ) \
(G_TYPE_INSTANCE_GET_CLASS( (obj), \
VIPS_TYPE_INTERPOLATE_BICUBIC, VipsInterpolateBicubicClass ))
typedef VipsInterpolate VipsInterpolateBicubic;
typedef VipsInterpolateClass VipsInterpolateBicubicClass;
/* Precalculated interpolation matrices. int (used for pel
* sizes up to short), and double (for all others). We go to
* scale + 1 so we can round-to-nearest safely.
*/
/* We could keep a large set of 2d 4x4 matricies, but this actually
* works out slower since for many resizes the thing will no longer
* fit in L1.
*/
static int vips_bicubic_matrixi[VIPS_TRANSFORM_SCALE + 1][4];
static double vips_bicubic_matrixf[VIPS_TRANSFORM_SCALE + 1][4];
/* We need C linkage for this.
*/
extern "C" {
G_DEFINE_TYPE( VipsInterpolateBicubic, vips_interpolate_bicubic,
VIPS_TYPE_INTERPOLATE );
}
/* Pointers to write to / read from, number of bands,
* how many bytes to add to move down a line.
*/
/* T is the type of pixels we are reading and writing.
*/
/* Fixed-point version, for 8 and 16-bit types.
*/
template <typename T, int min_value, int max_value>
static void inline
bicubic_int_tab( PEL *pout, const PEL *pin,
const int bands, const int lskip,
const int *cx, const int *cy )
{
T* restrict out = (T *) pout;
const T* restrict in = (T *) pin;
const int b1 = bands;
const int b2 = b1 + b1;
const int b3 = b1 + b2;
const int l1 = lskip / sizeof( T );
const int l2 = l1 + l1;
const int l3 = l1 + l2;
const int l1_plus_b1 = l1 + b1;
const int l1_plus_b2 = l1 + b2;
const int l1_plus_b3 = l1 + b3;
const int l2_plus_b1 = l2 + b1;
const int l2_plus_b2 = l2 + b2;
const int l2_plus_b3 = l2 + b3;
const int l3_plus_b1 = l3 + b1;
const int l3_plus_b2 = l3 + b2;
const int l3_plus_b3 = l3 + b3;
for( int z = 0; z < bands; z++ ) {
const T uno_one = in[0];
const T uno_two = in[b1];
const T uno_thr = in[b2];
const T uno_fou = in[b3];
const T dos_one = in[l1];
const T dos_two = in[l1_plus_b1];
const T dos_thr = in[l1_plus_b2];
const T dos_fou = in[l1_plus_b3];
const T tre_one = in[l2];
const T tre_two = in[l2_plus_b1];
const T tre_thr = in[l2_plus_b2];
const T tre_fou = in[l2_plus_b3];
const T qua_one = in[l3];
const T qua_two = in[l3_plus_b1];
const T qua_thr = in[l3_plus_b2];
const T qua_fou = in[l3_plus_b3];
int bicubic = bicubic_int<T>(
uno_one, uno_two, uno_thr, uno_fou,
dos_one, dos_two, dos_thr, dos_fou,
tre_one, tre_two, tre_thr, tre_fou,
qua_one, qua_two, qua_thr, qua_fou,
cx, cy );
if( bicubic < min_value )
bicubic = min_value;
else if( bicubic > max_value )
bicubic = max_value;
out[z] = bicubic;
in += 1;
}
}
/* Floating-point version, for int/float types.
*/
template <typename T>
static void inline
bicubic_float_tab( PEL *pout, const PEL *pin,
const int bands, const int lskip,
const double *cx, const double *cy )
{
T* restrict out = (T *) pout;
const T* restrict in = (T *) pin;
const int b1 = bands;
const int b2 = b1 + b1;
const int b3 = b1 + b2;
const int l1 = lskip / sizeof( T );
const int l2 = l1 + l1;
const int l3 = l1 + l2;
const int l1_plus_b1 = l1 + b1;
const int l1_plus_b2 = l1 + b2;
const int l1_plus_b3 = l1 + b3;
const int l2_plus_b1 = l2 + b1;
const int l2_plus_b2 = l2 + b2;
const int l2_plus_b3 = l2 + b3;
const int l3_plus_b1 = l3 + b1;
const int l3_plus_b2 = l3 + b2;
const int l3_plus_b3 = l3 + b3;
for( int z = 0; z < bands; z++ ) {
const T uno_one = in[0];
const T uno_two = in[b1];
const T uno_thr = in[b2];
const T uno_fou = in[b3];
const T dos_one = in[l1];
const T dos_two = in[l1_plus_b1];
const T dos_thr = in[l1_plus_b2];
const T dos_fou = in[l1_plus_b3];
const T tre_one = in[l2];
const T tre_two = in[l2_plus_b1];
const T tre_thr = in[l2_plus_b2];
const T tre_fou = in[l2_plus_b3];
const T qua_one = in[l3];
const T qua_two = in[l3_plus_b1];
const T qua_thr = in[l3_plus_b2];
const T qua_fou = in[l3_plus_b3];
const T bicubic = bicubic_float<T>(
uno_one, uno_two, uno_thr, uno_fou,
dos_one, dos_two, dos_thr, dos_fou,
tre_one, tre_two, tre_thr, tre_fou,
qua_one, qua_two, qua_thr, qua_fou,
cx, cy );
out[z] = bicubic;
in += 1;
}
}
/* Ultra-high-quality version for double images.
*/
template <typename T>
static void inline
bicubic_notab( PEL *pout, const PEL *pin,
const int bands, const int lskip,
double x, double y )
{
T* restrict out = (T *) pout;
const T* restrict in = (T *) pin;
const int b1 = bands;
const int b2 = b1 + b1;
const int b3 = b1 + b2;
const int l1 = lskip / sizeof( T );
const int l2 = l1 + l1;
const int l3 = l1 + l2;
const int l1_plus_b1 = l1 + b1;
const int l1_plus_b2 = l1 + b2;
const int l1_plus_b3 = l1 + b3;
const int l2_plus_b1 = l2 + b1;
const int l2_plus_b2 = l2 + b2;
const int l2_plus_b3 = l2 + b3;
const int l3_plus_b1 = l3 + b1;
const int l3_plus_b2 = l3 + b2;
const int l3_plus_b3 = l3 + b3;
double cx[4];
double cy[4];
calculate_coefficients_catmull( x, cx );
calculate_coefficients_catmull( y, cy );
for( int z = 0; z < bands; z++ ) {
const T uno_one = in[0];
const T uno_two = in[b1];
const T uno_thr = in[b2];
const T uno_fou = in[b3];
const T dos_one = in[l1];
const T dos_two = in[l1_plus_b1];
const T dos_thr = in[l1_plus_b2];
const T dos_fou = in[l1_plus_b3];
const T tre_one = in[l2];
const T tre_two = in[l2_plus_b1];
const T tre_thr = in[l2_plus_b2];
const T tre_fou = in[l2_plus_b3];
const T qua_one = in[l3];
const T qua_two = in[l3_plus_b1];
const T qua_thr = in[l3_plus_b2];
const T qua_fou = in[l3_plus_b3];
const T bicubic = bicubic_float<T>(
uno_one, uno_two, uno_thr, uno_fou,
dos_one, dos_two, dos_thr, dos_fou,
tre_one, tre_two, tre_thr, tre_fou,
qua_one, qua_two, qua_thr, qua_fou,
cx, cy );
out[z] = bicubic;
in += 1;
}
}
static void
vips_interpolate_bicubic_interpolate( VipsInterpolate *interpolate,
PEL *out, REGION *in, double x, double y )
{
/* Find the mask index. We round-to-nearest, so we need to generate
* indexes in 0 to VIPS_TRANSFORM_SCALE, 2^n + 1 values. We multiply
* by 2 more than we need to, add one, mask, then shift down again to
* get the extra range.
*/
const int sx = x * VIPS_TRANSFORM_SCALE * 2;
const int sy = y * VIPS_TRANSFORM_SCALE * 2;
const int six = sx & (VIPS_TRANSFORM_SCALE * 2 - 1);
const int siy = sy & (VIPS_TRANSFORM_SCALE * 2 - 1);
const int tx = (six + 1) >> 1;
const int ty = (siy + 1) >> 1;
/* We know x/y are always positive, so we can just (int) them.
*/
const int ix = (int) x;
const int iy = (int) y;
/* Look up the tables we need.
*/
const int *cxi = vips_bicubic_matrixi[tx];
const int *cyi = vips_bicubic_matrixi[ty];
const double *cxf = vips_bicubic_matrixf[tx];
const double *cyf = vips_bicubic_matrixf[ty];
/* Back and up one to get the top-left of the 4x4.
*/
const PEL *p = (PEL *) IM_REGION_ADDR( in, ix - 1, iy - 1 );
/* Pel size and line size.
*/
const int bands = in->im->Bands;
const int lskip = IM_REGION_LSKIP( in );
#ifdef DEBUG
printf( "vips_interpolate_bicubic_interpolate: %g %g\n", x, y );
printf( "\tleft=%d, top=%d, width=%d, height=%d\n",
ix - 1, iy - 1, 4, 4 );
printf( "\tmaskx=%d, masky=%d\n", tx, ty );
#endif /*DEBUG*/
switch( in->im->BandFmt ) {
case IM_BANDFMT_UCHAR:
bicubic_int_tab<unsigned char, 0, UCHAR_MAX>(
out, p, bands, lskip,
cxi, cyi );
/*
Handy for benchmarking
bicubic_float_tab<unsigned char>(
out, p, bands, lskip,
cxf, cyf );
bicubic_notab<unsigned char>(
out, p, bands, lskip,
x - ix, y - iy );
*/
break;
case IM_BANDFMT_CHAR:
bicubic_int_tab<signed char, SCHAR_MIN, SCHAR_MAX>(
out, p, bands, lskip,
cxi, cyi );
break;
case IM_BANDFMT_USHORT:
bicubic_int_tab<unsigned short, 0, USHRT_MAX>(
out, p, bands, lskip,
cxi, cyi );
break;
case IM_BANDFMT_SHORT:
bicubic_int_tab<signed short, SHRT_MIN, SHRT_MAX>(
out, p, bands, lskip,
cxi, cyi );
break;
case IM_BANDFMT_UINT:
bicubic_float_tab<unsigned int>( out, p, bands, lskip,
cxf, cyf );
break;
case IM_BANDFMT_INT:
bicubic_float_tab<signed int>( out, p, bands, lskip,
cxf, cyf );
break;
case IM_BANDFMT_FLOAT:
bicubic_float_tab<float>( out, p, bands, lskip,
cxf, cyf );
break;
case IM_BANDFMT_DOUBLE:
bicubic_notab<double>( out, p, bands, lskip,
x - ix, y - iy );
break;
case IM_BANDFMT_COMPLEX:
bicubic_float_tab<float>( out, p, bands * 2, lskip,
cxf, cyf );
break;
case IM_BANDFMT_DPCOMPLEX:
bicubic_notab<double>( out, p, bands * 2, lskip,
x - ix, y - iy );
break;
default:
break;
}
}
static void
vips_interpolate_bicubic_class_init( VipsInterpolateBicubicClass *iclass )
{
VipsObjectClass *object_class = VIPS_OBJECT_CLASS( iclass );
VipsInterpolateClass *interpolate_class =
VIPS_INTERPOLATE_CLASS( iclass );
object_class->nickname = "bicubic";
object_class->description = _( "Bicubic interpolation (Catmull-Rom)" );
interpolate_class->interpolate = vips_interpolate_bicubic_interpolate;
interpolate_class->window_size = 4;
interpolate_class->window_offset = 2;
/*
* Note from nicolas: If things were programmed sanely, I
* think window_offset should be 1, not 2.
*/
/* Build the tables of pre-computed coefficients.
*/
for( int x = 0; x < VIPS_TRANSFORM_SCALE + 1; x++ ) {
calculate_coefficients_catmull(
(float) x / VIPS_TRANSFORM_SCALE,
vips_bicubic_matrixf[x] );
for( int i = 0; i < 4; i++ )
vips_bicubic_matrixi[x][i] =
vips_bicubic_matrixf[x][i] *
VIPS_INTERPOLATE_SCALE;
}
}
static void
vips_interpolate_bicubic_init( VipsInterpolateBicubic *bicubic )
{
#ifdef DEBUG
printf( "vips_interpolate_bicubic_init: " );
vips_object_print( VIPS_OBJECT( bicubic ) );
#endif /*DEBUG*/
}