606 lines
18 KiB
C
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);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|