Files
argyllcms/imdi/imdi.c
2026-08-20 20:28:40 +01:00

606 lines
18 KiB
C

/* Integer Multi-Dimensional Interpolation */
/*
* Copyright 2000 - 2007 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.
*/
/*
* This is the implementation of the run time code.
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <stdarg.h>
#include <string.h>
#include "imdi.h"
#include "imdi_tab.h"
#include "imdi_k.h" /* Declaration of all the kernel functions */
#undef VERBOSE
#undef VVERBOSE
static unsigned int imdi_get_check(imdi *im);
static void imdi_reset_check(imdi *im);
static void imdi_info(imdi *s, unsigned long *size, int *gres, int *sres);
static void imdi_del(imdi *im);
static void interp_match(imdi *s, void **outp, int outst, void **inp, int inst,
unsigned int npixels);
/* Create a new imdi */
/* Return NULL if request is not supported */
/* Note that we use the high level pixel layout description to locate */
/* a suitable run-time. */
imdi *new_imdi(
int id, /* Number of input dimensions */
int od, /* Number of output lookup dimensions */
/* Number of output channels written = od - no. of oopt skip flags */
imdi_pixrep in, /* Input pixel representation */
int in_signed, /* Bit flag per channel, NZ if treat as signed */
int *inm, /* Input raster channel to callback channel mapping, NULL for none. */
imdi_iprec iprec, /* Internal processing precision */
imdi_pixrep out, /* Output pixel representation */
int out_signed, /* Bit flag per channel, NZ if treat as signed */
int *outm, /* Output raster channel to callback channel mapping, NULL for none. */
/* Mapping must include skipped channels. */
int res, /* Desired table resolution */
imdi_ooptions oopt, /* Output per channel options (by callback channel) */
unsigned int *checkv, /* Output channel check values (by callback channel, NULL == 0) */
imdi_options opt, /* Direction and stride options */
/* Callbacks to lookup the imdi table values. */
/* (Skip output channels are looked up) */
void (*input_curves) (void *cntx, double *out_vals, double *in_vals),
void (*md_table) (void *cntx, double *out_vals, double *in_vals),
void (*output_curves)(void *cntx, double *out_vals, double *in_vals),
void *cntx /* Context to callbacks */
) {
int i;
int prec; /* Target internal precision */
int bk = -1; /* Best table */
int bfig = 0x7fffffff; /* Best tables figure of merit */
int bstres = 0; /* Best tables target stres */
genspec gs; /* Generation specification */
tabspec ts; /* Table specifications */
genspec bgs; /* Best gen spec */
tabspec bts; /* Best tab spec */
imdi_conv bcnv = conv_none; /* Best tables conversion flags */
imdi_ooptions Ooopt; /* oopt re-aranged to correspond to output channel index */
imdi *im;
/* Compute the Output channel index oopt mask */
if (outm == NULL)
Ooopt = oopt;
else {
int ff;
Ooopt = 0;
for (i = 0; i < od; i++) {
ff = OOPTX(oopt,outm[i]);
Ooopt |= OOPT(ff,i);
}
}
#ifdef VERBOSE
printf("new_imdi called with id %d, od %d, res %d Ooopt 0x%x, opt 0x%x\n", id, od, res, Ooopt, opt);
printf("about to checking %d kernels\n", no_kfuncs);
#endif
/* Figure out the internal precision requested */
COMPUTE_IPREC(prec, in, iprec, out)
/* Zero out the gen and tabspecs, to give diff a place to start */
memset((void *)&gs, 0, sizeof(genspec));
memset((void *)&ts, 0, sizeof(tabspec));
/* The first thing to do is see if there is an available kernel function */
for (i = 0; i < no_kfuncs; i++) {
int stres; /* Computed stres needed */
imdi_conv cnv = conv_none; /* Conversions needed for this choice */
int fig; /* Figure of merit - smaller is better */
ktable[i].gentab(&gs, &ts); /* Udate the kernel functions genspec and tabspec */
#ifdef VERBOSE
printf("\n");
printf("kernel %d has id %d, od %d, irep %d orep %d oopt 0x%x, opt 0x%x\n",
i, gs.id, gs.od, gs.irep, gs.orep, gs.oopt, gs.opt);
printf("Input req is id %d, od %d, irep %d orep %d, oopt 0x%x, opt 0x%x\n",
id, od, in, out, Ooopt, opt);
#endif
/* First check mandatory things */
if (!(
id == gs.id /* Input dimension */
&& od == gs.od /* Output dimension */
)) {
#ifdef VVERBOSE
printf(" Input or Output dimension mismatch\n");
#endif
continue;
}
/* Check if we have an exact or conversion match for input stride */
if (!(
(opt & opts_istride) == (gs.opt & opts_istride)
|| (!(opt & opts_istride) && (gs.opt & opts_istride))
)) {
#ifdef VVERBOSE
printf(" Input stride mismatch\n");
#endif
continue;
}
/* Check if we have an exact or conversion match for output stride */
if (!(
(opt & opts_ostride) == (gs.opt & opts_ostride)
|| (!(opt & opts_ostride) && (gs.opt & opts_ostride))
)) {
#ifdef VVERBOSE
printf(" Output stride mismatch\n");
#endif
continue;
}
/* Check if we have an exact or conversion match for input representation */
if (!(
in == gs.irep
|| (in == pixint8 && gs.irep == planeint8 && (gs.opt & opts_istride))
|| (in == pixint16 && gs.irep == planeint16 && (gs.opt & opts_istride))
)) {
#ifdef VVERBOSE
printf(" Input representation mismatch\n");
#endif
continue;
}
/* Check if we have an exact or conversion match for output representation */
if (!(
out == gs.orep
|| (out == pixint8 && gs.orep == planeint8 && (gs.opt & opts_ostride))
|| (out == pixint16 && gs.orep == planeint16 && (gs.opt & opts_ostride))
)) {
#ifdef VVERBOSE
printf(" Output representation mismatch\n");
#endif
continue;
}
/* See if we have the output per channel options needed */
if (!((Ooopt & gs.oopt) == Ooopt)) {
#ifdef VVERBOSE
printf(" Output per channel options mismatch\n");
#endif
continue;
}
/* See if we have an exact or conversion match for reverse direction */
if (!(
((!(opt & opts_bwd) && !(opt & opts_fwd))) /* Don't care */
|| (((opt & opts_bwd) && (gs.opt & opts_bwd))) /* Match */
|| (((opt & opts_fwd) && (gs.opt & opts_fwd))) /* Match */
|| ((gs.opt & opts_istride) && (gs.opt & opts_ostride)) /* Can convert */
)) {
#ifdef VVERBOSE
printf(" Direction mismatch\n");
#endif
continue;
}
#ifdef VERBOSE
printf(" found match\n");
#endif
fig = 0;
/* Apply penalty if we will have to do a conversion match */
if ((opt & opts_istride) != (gs.opt & opts_istride)) {
cnv |= conv_istr;
fig += 1000;
#ifdef VERBOSE
printf(" needs input stride conversion though\n");
#endif
}
if ((opt & opts_ostride) != (gs.opt & opts_ostride)) {
cnv |= conv_ostr;
fig += 1000;
#ifdef VERBOSE
printf(" needs output stride conversion though\n");
#endif
}
if (in != gs.irep) {
cnv |= conv_irep;
fig += 5000;
#ifdef VERBOSE
printf(" needs input representation conversion though\n");
#endif
}
if (out != gs.orep) {
cnv |= conv_orep;
fig += 5000;
#ifdef VERBOSE
printf(" needs output representation conversion though\n");
#endif
}
/* If we don't need output checking or skipping, but we've got it */
if ((~Ooopt & gs.oopt) != 0) {
unsigned int tmp = (~Ooopt & gs.oopt);
/* Count number of bits set that we don't need */
/* (From HACKMEM 169) */
tmp = tmp - ((tmp >> 1) & 033333333333) - ((tmp >> 2) & 011111111111);
tmp = ((tmp + (tmp >> 3)) & 030707070707) % 63;
fig += tmp;
#ifdef VERBOSE
printf(" has output options we don't need though\n");
#endif
}
/* If we've got output skipping, we need to skip pointers in interp. */
if ((Ooopt & OOPTS_SKIP) != 0) {
cnv |= conv_skip;
}
if (((opt & opts_fwd) && (gs.opt & opts_bwd))
|| ((opt & opts_bwd) && (gs.opt & opts_fwd))) {
cnv |= conv_rev;
fig += 1000;
#ifdef VERBOSE
printf(" needs direction conversion though\n");
#endif
}
if (prec != gs.prec) { /* Internal precision mismatch */
fig += 100000; /* Will have a major effect, so discourage using it */
#ifdef VERBOSE
printf(" internal precision doesn't match though (want %d, got %d)\n",prec,gs.prec);
#endif
}
/* If we've got a choice of algorithm possible */
if (gs.opt & (opts_splx_sort | opts_sort_splx)) {
/* If we've got a preference of algorithm, */
if ((opt & (opts_splx_sort | opts_sort_splx))) {
/* and there is a mismatch */
if (((opt & opts_splx_sort) && ts.sort != 0)
|| ((opt & opts_sort_splx) && ts.sort == 0)) {
fig += 10000; /* Will have a great effect, so discourage using it */
#ifdef VERBOSE
printf(" sort/simplex algorithm mismatch\n");
#endif
}
} else { /* There is no preference chosen at run time, */
/* and there is a mismatch to the compile time preference */
if (((gs.opt & opts_splx_sort) && ts.sort != 0)
|| ((gs.opt & opts_sort_splx) && ts.sort == 0)) {
fig += 10000; /* Will have a great effect, so discourage using it */
#ifdef VERBOSE
printf(" sort/simplex algorithm mismatch\n");
#endif
}
}
}
if (ts.sort) {
stres = 0;
#ifdef VERBOSE
printf("gres = %d, gs.gres = %d\n",res,gs.itres);
#endif
/* We want one that is equals or exceeds the desired */
/* resolution, but doesn't exceed it too much, or the code */
/* will be inefficient. */
/* If there are no routines that can meet the desired precision, */
/* then it is ok to use the one closest to the desired precision. */
if (gs.itres >= res) {
fig += 10 * (gs.itres - res);
} else {
fig += 10000 + 10 * (res - gs.itres);
}
} else {
/* compute the needed stres (Assuming not sort) */
stres = ((1 << gs.prec)-1 + res-2)/(res-1);
#ifdef VERBOSE
printf("gres = %d, sres = %d, gs.gres = %d, gs.sres = %d\n",res,stres,gs.itres,gs.stres);
#endif
/* We want one that is equals or exceeds the desired */
/* resolution, but doesn't exceed it too much, or the code */
/* will be inefficient. */
/* If there are no routines that can meet the desired precision, */
/* then it is ok to use the one closest to the desired precision. */
if (gs.itres >= res && gs.stres >= stres) {
fig += 10 * (gs.itres - res) + (gs.stres - stres);
} {
if (gs.itres < res) {
fig += 10000 + 10 * (res - gs.itres);
}
if (gs.stres < stres) {
fig += 1000 + 10 * (stres - gs.stres);
}
}
}
#ifdef VERBOSE
printf(" figure of merit %d\n",fig);
#endif
/* Is this the best one so far ? */
if (fig < bfig) {
bfig = fig;
bk = i;
bstres = stres;
bgs = gs; /* Structure copy */
bts = ts; /* Structure copy */
bcnv = cnv;
#ifdef VERBOSE
printf(" best so far\n");
#endif
}
}
if (bk < 0) {
#ifdef VERBOSE
printf("new_imdi failed - dimensionality or representations couldn't be matched\n");
#endif
return NULL; /* Nothing matches */
}
if ((im = (imdi *)calloc(1, sizeof(imdi))) == NULL) {
#ifdef VERBOSE
printf("new_imdi malloc imdi failed\n");
#endif
/* Should we return an error somehow ? */
return NULL;
}
/* We've decided kernel function bk is going to be the best, */
/* so now setup the appropriate tables to use with it. */
if (bgs.itres > res)
bgs.itres = res; /* Tell table create what the res is */
if (bgs.stres > bstres)
bgs.stres = bstres;
/* Tel table setup how to treat integer input in per channel lookups */
bgs.in_signed = in_signed;
bgs.out_signed = out_signed;
#ifdef VERBOSE
if (!bts.sort) {
if ((bgs.stres * (bgs.itres-1)) < ((1 << bgs.prec)-1)) {
printf("Warning: table chosen doesn't reach desired precision!\n");
printf("Wanted %d, got %d\n", ((1 << bgs.prec)-1), (bgs.stres * (bgs.itres-1)));
}
}
#endif
/* Allocate and initialise the appropriate tables */
im->impl = (void *)imdi_tab(&bgs, &bts, bcnv, in, out, ktable[bk].interp,
inm, outm, oopt, checkv, input_curves, md_table,
output_curves, cntx);
if (im->impl == NULL) {
#ifdef VERBOSE
printf("imdi_tab failed\n");
#endif
imdi_del(im);
return NULL;
}
#ifdef VERBOSE
if (bcnv != conv_none)
printf("imdi_tab: using a runtime match, cnv flags 0x%x\n",bcnv);
#endif
if (bcnv == conv_none) /* No runtime match conversion needed */
im->interp = ktable[bk].interp;
else
im->interp = interp_match;
im->get_check = imdi_get_check;
im->reset_check = imdi_reset_check;
im->info = imdi_info;
im->del = imdi_del;
#ifdef VERBOSE
printf("new_imdi returning 0x%x\n", im);
#endif
return im;
}
/* Runtime matching adapter */
/* We can emulate many combinations of interpolation function */
/* by converting to a routine that supports stride, or one that */
/* supports plane interleaved and stride. */
/* This also lets us emulate functions that can convert between */
/* pixel and plane interleaved at the same time as doing the */
/* color interpolation. Warp is in components (NOT bytes) */
/* We cope with skipping writes to output channels by */
/* only implementing that support in plane interleaved, */
/* and depending on that being used for pixel interleaved. */
static void interp_match(
imdi *s,
void **outp, int outst, /* Output pointers and stride */
void **inp, int inst, /* Input pointers and stride */
unsigned int npixels /* Number of pixels */
) {
int j, i;
void *minp[IXDI];
void *moutp[IXDI];
imdi_imp *impl = (imdi_imp *)s->impl;
imdi_conv cnv = impl->cnv;
/* Need to supply default stride. */
/* This is simply the input dimension for pixel interleaved, */
/* and 1 for plane interleaved. */
if (cnv & conv_istr) {
if (impl->cirep == pixint8 || impl->cirep == pixint16)
inst = impl->id;
else
inst = 1;
}
if (cnv & conv_ostr) {
if (impl->corep == pixint8 || impl->corep == pixint16)
outst = impl->wod;
else
outst = 1;
}
/* Convert from pixel to plane by setting the plane pointers */
/* to each pixel component, and using the pixel interleaved stride */
if (cnv & conv_irep) {
if (impl->cirep == pixint8) { /* Convert from pix8 to plane8 */
for (j = 0; j < impl->id; j++)
minp[j] = (void *)((char *)inp[0] + j);
} else if (impl->cirep == pixint16) { /* Convert from pix16 to plane16 */
for (j = 0; j < impl->id; j++)
minp[j] = (void *)((char *)inp[0] + 2 * j);
}
} else { /* Copy pointers, because we call with them. */
if (impl->cirep == pixint8 || impl->cirep == pixint16) {
minp[0] = inp[0];
} else {
for (j = 0; j < impl->id; j++)
minp[j] = inp[j];
}
}
if (cnv & conv_orep) {
if (impl->corep == pixint8) { /* Convert from pix8 to plane8 */
for (j = i = 0; j < impl->od; j++) {
if ((impl->skipf & (1 << j)) == 0) /* If not skipped */
moutp[j] = (void *)((char *)outp[0] + i++);
else
moutp[j] = NULL;
}
} else if (impl->corep == pixint16) { /* Convert from pix16 to plane16 */
for (j = i = 0; j < impl->od; j++)
if ((impl->skipf & (1 << j)) == 0) /* If not skipped */
moutp[j] = (void *)((char *)outp[0] + 2 * i++);
else
moutp[j] = NULL;
}
} else { /* Copy pointers, because we call with them. */
if (impl->corep == pixint8 || impl->corep == pixint16) {
moutp[0] = outp[0];
} else { /* Plane interleaved */
for (j = i = 0; j < impl->od; j++) {
if ((impl->skipf & (1 << j)) == 0) /* If not skipped */
moutp[j] = outp[i++];
else
moutp[j] = NULL;
}
}
}
/* Convert fwd function to a backwards one, or visa-versa. */
/* Move the pointers to the last pixel, and reverse the stride */
if (cnv & conv_rev) {
if (impl->firep == pixint8) {
minp[0] = (void *)((char *)minp[0] + inst * (npixels - 1));
} else if (impl->firep == pixint16) {
minp[0] = (void *)((char *)minp[0] + inst * 2 * (npixels - 1));
} else if (impl->firep == planeint8) {
for (j = 0; j < impl->id; j++)
minp[j] = (void *)((char *)minp[j] + inst * (npixels - 1));
} else if (impl->firep == planeint16) {
for (j = 0; j < impl->id; j++)
minp[j] = (void *)((char *)minp[j] + inst * 2 * (npixels - 1));
}
inst = -inst;
if (impl->forep == pixint8) {
moutp[0] = (void *)((char *)moutp[0] + outst * (npixels - 1));
} else if (impl->forep == pixint16) {
moutp[0] = (void *)((char *)moutp[0] + outst * 2 * (npixels - 1));
} else if (impl->forep == planeint8) {
for (j = 0; j < impl->od; j++)
moutp[j] = (void *)((char *)moutp[j] + outst * (npixels - 1));
} else if (impl->forep == planeint16) {
for (j = 0; j < impl->od; j++)
moutp[j] = (void *)((char *)moutp[j] + outst * 2 * (npixels - 1));
}
outst = -outst;
}
/* Call the conversion routine we have */
impl->interp(s, moutp, outst, minp, inst, npixels);
}
/* Get the per output channel check flags - bit corresponds to output interpolation channel */
static unsigned int imdi_get_check(imdi *im) {
imdi_imp *impl = (imdi_imp *)im->impl;
unsigned int rv;
int i;
/* Convert from output channel index to callback index */
for (rv = 0, i = 0; i < impl->od; i++) {
unsigned int b;
b = 1 & (impl->checkf >> i); /* Output index flag */
rv |= (b << impl->im_map[i]); /* to callback index */
}
return rv;
}
/* Reset the output check flags (not reset by interp) */
static void imdi_reset_check(imdi *im) {
imdi_imp *impl = (imdi_imp *)im->impl;
impl->checkf = 0;
}
/* Return some information about the imdi */
static void imdi_info(imdi *im, unsigned long *psize, int *pgres, int *psres) {
imdi_imp *impl = (imdi_imp *)im->impl;
unsigned long size;
size = sizeof(imdi);
if (impl != NULL) {
size += impl->size;
if (psize != NULL)
*psize = size;
if (pgres != NULL)
*pgres = impl->gres;
if (psres != NULL)
*psres = impl->sres;
}
}
/* Delete the object */
static void imdi_del(imdi *im) {
/* Free all the allocated tables */
if (im->impl != NULL)
imdi_tab_free((imdi_imp *)im->impl);
/* Free this structure */
free(im);
}