Files
argyllcms/rspl/spline.c
T
2026-08-20 20:28:40 +01:00

350 lines
9.8 KiB
C

/*
* Argyll Color Management System
* Multi-dimensional regularized spline data structure
*
* Spline forward interpolation support.
*
* Author: Graeme W. Gill
* Date: 12/10/98
*
* Copyright 1998, Graeme W. Gill
* All rights reserved.
*
* This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
* see the License.txt file for licencing details.
*/
/* TTBD:
Get rid of error() calls - return status instead
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <math.h>
#include <time.h>
#include "rspl_imp.h"
#include "numlib.h"
int spline_interp_rspl(rspl *ss, co *cp);
#undef DEBUG
#undef NEVER
#define ALWAYS
/* Convention is to use:
i to index grid points u.a
n to index data points d.a
e to index position dimension di
f to index output function dimension fdi
j misc and cube corners
k misc
*/
/* ====================================================== */
/* Init spline elements in rspl */
void
init_spline(rspl *s) {
s->spline.nm = 0;
s->spline.spline = 0;
s->spline.magic = NULL;
s->spline_interp = spline_interp_rspl;
}
/* Free up the spline interpolation info */
void free_spline(
rspl *s /* Pointer to rspl grid */
) {
if (s->spline.magic != NULL) {
free(s->spline.magic);
}
s->spline.nm = 0;
s->spline.spline = 0;
}
/* ====================================================== */
/* Setup functions first: */
/* Hermite spline, magic matrix */
/* Indexes are: param powers 0, 1, 2, 3; Offset from base vertex 0,1; Dimension mask 0,1 */
static double hmagic[4][2][2] = {
{ { 1.0, 0.0}, { 0.0, 0.0} },
{ { 0.0, 1.0}, { 0.0, 0.0} },
{ {-3.0, -2.0}, { 3.0, -1.0} },
{ { 2.0, 1.0}, {-2.0, 1.0} }
};
/* Allocate and initialize tangency information for each grid point */
static void make_tang(
rspl *s /* Pointer to rspl grid */
) {
int i,p,j;
int di = s->di;
int fdi = s->fdi;
int nig = s->g.no;
float *tp; /* Pointer to tangent values */
int nim, mix; /* Number in magic, magic index */
float *tang_alloc, *tang; /* Tangency info */
float *gt; /* Working grid point */
//printf("~~make_tang called\n");
/* Organized as: tang[[grid]][di combs.][fdi] */
/* Allocate space for tangency info */
if ((tang_alloc = (float *) malloc(sizeof(float) * nig * (((1 << di) * fdi)+G_XTRA))) == NULL)
error("rspl malloc failed - tangecy points");
tang = tang_alloc + G_XTRA; /* Offset for flags and non-mono error */
/* For all grid points */
for (tp = tang, gt = s->g.a, i = 0; i < nig; i++, gt += s->g.pss, tp += G_XTRA) {
int ee;
/* printf("\n~~ grid point %d\n",i); */
/* Look at surrounding grid points in combinations of +- 1 all dimensions */
for (ee = 0; ee < (1 << di); ee++) {
double av[MXRO]; /* average */
int nia = 0; /* Number in average */
int f, ec;
/* printf("Dim combo %d\n",ee); */
/* special case - base value */
if (ee == 0) {
*((int *)(tp-2)) = *((int *)(gt-2)); /* Copy flags */
tp[-1] = gt[-1]; /* Copy ink limit function value */
for (f = 0; f < fdi; f++) {
*tp++ = gt[f];
/* printf("Tang value out %d = %f\n",f,tp[-1]); */
}
continue;
}
for (f = 0; f < fdi; f++)
av[f] = 0.0; /* Init average */
/* For all surroundin grid points in this combination */
for (ec = 0; ec < (1 << di); ec++) {
int xo, io, sgn, e, ex;
/* printf("~~checking out surrounding combo %d\n",ec); */
if (ec & ~ee) {
/* printf("~~being skipped\n"); */
continue; /* Skip invalid combo */
}
xo = io = 0; /* Grid float offset */
sgn = 1; /* Sign */
ex = 0; /* Flag - No extrapolation */
for (e = 0; e < di; e++) { /* For each dimension */
/* printf("~~checking dimension %d\n",e); */
if (!(ee & (1 << e))) {
/* printf("~~dimension not active\n"); */
continue; /* Dimension is not active */
}
if (ec & (1 << e)) {
/* If + dimension is valid */
if (((G_FL(gt,e) & 3) > 0) || (G_FL(gt,e) & 0x4)) {
int to = s->g.fci[e]; /* +1 in dimension */
io += to; /* real/pivot point */
xo += to; /* reflected point */
} else {
ex = 1; /* Use extrapolation */
xo -= s->g.fci[e]; /* -1 in dimension */
}
} else {
sgn = -sgn; /* Reverse sign */
/* If - dimension is valid */
if (((G_FL(gt,e) & 3) > 0) || !(G_FL(gt,e) & 0x4)) {
int to = -s->g.fci[e]; /* -1 in dimension */
io += to; /* real/pivot point */
xo += to; /* reflected point */
} else {
ex = 1; /* Use extrapolation */
xo += s->g.fci[e]; /* +1 in dimension */
}
}
}
/* Add surrounding grid points value into the average */
if (!ex) {
for (f = 0; f < fdi; f++)
av[f] += (double)sgn * gt[io + f];
} else { /* Extrapolate point beyond edge */
/* Use an extrapolation that tries to maintain curvature */
for (f = 0; f < fdi; f++) {
double v0,v1,v2;
v0 = gt[io + f]; /* Pivot point */
v1 = gt[xo + f]; /* Reflection of target in pivot */
v2 = gt[2 * xo - io + f]; /* Reflection +2 */
av[f] += (double)sgn * (3.0 * (v0 - v1) + v2);
}
}
nia++;
}
for (f = 0; f < fdi; f++) {
*tp++ = (float)(av[f]/(double)nia);
/* printf("Tang value out %d = %f, average of %d\n",f,tp[-1],nia); */
}
} /* Next dimension combination */
} /* Next grid point */
/* Create a full sized hermite magic matrix */
/* Organized as: magic[4^di][2^di][2^di] */
/* = [param power combos][cube vertex index][di combos], */
/* but then only store non-zero weight values. */
for (i = 0, nim = 1; i < di; nim *= 10, i++); /* Number of entries needed */
if (s->spline.magic == NULL) { /* Allocate space for magic matrix info */
if ((s->spline.magic = (magic_data *) malloc(sizeof(magic_data) * nim)) == NULL)
error("rspl malloc failed - hermite magic matrix data");
}
mix = 0;
for (p = 0; p < (1 << (2 * di)); p++) { /* For all combinations of parameter powers */
for (i = 0; i < (1 << di); i++) { /* For all corners of cube */
for (j = 0; j < (1 << di); j++) { /* For all dimension combinations */
int ii;
double wgt = 1.0;
for (ii = 0; ii < di; ii++) {
wgt *= hmagic[3&(p>>(2*ii))][1&(i>>ii)][1&(j>>ii)];
}
if (wgt != 0.0) { /* record non-zero weight value */
s->spline.magic[mix].p = p;
s->spline.magic[mix].i = i;
s->spline.magic[mix].j = fdi * j; /* Pre-scale */
s->spline.magic[mix].wgt = (float)wgt;
mix++;
}
}
}
}
/* mix should == nim! */
s->spline.nm = nim;
/* Free basic grid info, and substitute tangency enhanced version */
/* ~~~~!! need to free any other structures in rspl that depend on */
/* ~~~~!! g.pss size, ie. rev stuff ??? */
if (s->g.alloc != NULL)
free((void *)s->g.alloc);
s->g.alloc = tang_alloc;
s->g.a = tang;
/* Adjust index tables */
s->g.pss = (1 << di) * fdi + G_XTRA;
for (i = 0; i < di; i++)
s->g.fci[i] = s->g.ci[i] * s->g.pss; /* In floats */
for (i = 0; i < (1 << di); i++)
s->g.fhi[i] = s->g.hi[i] * s->g.pss; /* In floats */
s->spline.spline = 1;
//printf("~~make_tang finished\n");
}
/* Do a Hermite spline smooth interpolation based on the finest grid */
/* (To do this more accurately, the data point interpolation within */
/* the grid itteration should be of the same order. This increases */
/* itteration complexity quite a bit, so we won't bother for the moment.) */
/* This code is not optimised for speed. */
/* Return 0 if OK, 1 if input was clipped to grid */
int spline_interp_rspl(
rspl *s,
co *cp /* Input value and returned function value */
) {
int e,f,p,i;
int di = s->di;
int fdi = s->fdi;
double ppw[MXRI][4]; /* Parameter powers of 0, 1, 2, 3 */
float *ga[POW2MXRI]; /* Pointers to grid cubes data in tang[] */
magic_data *tp; /* Pointer to items in magic matrix */
int rv = 0;
/* printf("~~smooth interp called\n"); */
/* This is a restricted size function */
if (di > MXRI)
error("rspl: spline can't handle di = %d",di);
if (fdi > MXRO)
error("rspl: spline can't handle fdi = %d",fdi);
if (s->spline.spline == 0) /* Compute tangent info if it doesn't exist */
make_tang(s);
/* Locate grid base point, and position with base cube */
ga[0] = s->g.a; /* Base pointer of cube */
for (e = 0; e < di; e++) {
double t, pe;
int mi, gres_1 = s->g.res[e]-1;
pe = cp->p[e];
if (pe < s->g.l[e]) { /* Clip to grid */
pe = s->g.l[e];
rv = 1;
}
if (pe > s->g.h[e]) {
pe = s->g.h[e];
rv = 1;
}
t = (pe - s->g.l[e])/s->g.w[e];
mi = (int)floor(t); /* Grid coordinate */
if (mi < 0) /* Limit to valid cube base index range */
mi = 0;
else if (mi >= gres_1)
mi = gres_1-1;
ga[0] += s->g.fci[e] * mi; /* Add offset in dimen */
t = t - (double)mi;; /* sub-cube offset = parameter in dimension e */
ppw[e][0] = 1.0; /* Powers of parameter */
ppw[e][1] = t;
ppw[e][2] = t * t;
ppw[e][3] = t * t * t;
}
/* Compute indexes into cube corners in tangent array */
for (i = 1; i < (1 << di); i++)
ga[i] = ga[0] + s->g.fhi[i];
/* Now compute the output values */
for (f = 0; f < fdi; f++) /* Zero output value sums */
cp->v[f] = 0.0;
/* For all non-zero combinations of parameter powers */
{
double ppc = -1000.0; /* Parameter power combination */
for (tp = s->spline.magic, p = -1; tp < &s->spline.magic[s->spline.nm]; tp++) {
double wgt; /* Magic matrix weight */
float *gp; /* Pointer to vertex data */
if (p != tp->p) { /* Param power needs re-calculating */
int pp;
p = tp->p;
for (ppc = 1.0, pp = 0; pp < di; pp++)
ppc *= ppw[pp][3&(p>>(2*pp))]; /* comb. of param powers value */
}
wgt = tp->wgt * ppc; /* matrix times parameter */
gp = ga[tp->i] + tp->j; /* Point to base of vertex data */
for (f = 0; f < fdi; f++) /* For all output values */
cp->v[f] += wgt * gp[f];
}
}
/* printf("~~smooth interp finished\n"); */
return rv;
}