Files
2026-08-20 20:28:40 +01:00

2757 lines
76 KiB
C

/*
* International Color Consortium Format Library (icclib)
*
* Author: Graeme W. Gill
* Date: 2022/12/15
* Version: 3.0.0
*
* Copyright 1997 - 2023 Graeme W. Gill
*
* This material is licensed with an "MIT" free use license:-
* see the License4.txt file in this directory for licensing details.
*/
/* icmPe transform implementation */
/* This is #included in icc.c */
static icmPe *icc_new_pe_imp(icc *p, icTagTypeSignature ttype,
icTagTypeSignature pttype, int rdff);
/* ---------------------------------------------------------- */
/* Dummy init() */
static int icmPeDummy_init(icmPe *p) {
return ICM_ERR_OK;
}
/* Compute attr for a PeSeq */
static int icmPeSeq_init(icmPeSeq *p) {
unsigned int i;
icmPeOp pop = -1;
int count[icmPeOp_complex+1] = { 0 };
/* Make sure all member Pe's are inited */
p->nncount = 0;
for (i = 0; i < p->count; i++) {
if (p->pe[i] != NULL) {
p->pe[i]->init(p->pe[i]);
p->nncount++;
}
}
/* Find first to set no chan */
/* (Note that if icmLut1 is not a NOP and the input */
/* channels should be other than 3, then the icmPeSeq */
/* inputChan will be set wrong by the below...) */
for (i = 0; i < p->count; i++) {
if (p->pe[i] != NULL && p->pe[i]->attr.op != icmPeOp_NOP) {
p->inputChan = p->pe[i]->inputChan;
break;
}
}
/* Find last to set no chan */
for (i = p->count; i-- > 0;) {
if (p->pe[i] != NULL && p->pe[i]->attr.op != icmPeOp_NOP) {
p->outputChan = p->pe[i]->outputChan;
break;
}
}
/* Compute implementation flags and count distict operation types */
p->attr.op = icmPeOp_NOP;
p->attr.fwd = 1;
p->attr.bwd = 1;
for (i = 0; i < p->count; i++) {
if (p->pe[i] != NULL && p->pe[i]->attr.op != icmPeOp_NOP) {
/* If op is not mergable, count it as distinct */
if (p->pe[i]->attr.op == icmPeOp_cLUT /* Not mergable operation */
|| p->pe[i]->attr.op == icmPeOp_fmt
|| p->pe[i]->attr.op == icmPeOp_complex
|| p->pe[i]->attr.op != pop) /* Not the same mergable op. */
count[p->pe[i]->attr.op]++;
pop = p->pe[i]->attr.op;
p->attr.fwd &= p->pe[i]->attr.fwd;
p->attr.bwd &= p->pe[i]->attr.bwd;
}
}
/* label the dominant top operation type, i.e. */
/* perch/NOP -> dom-op -> perch/NOP */
/* If there is nothing other than NOP, label it as NOP */
if (count[icmPeOp_perch] == 0
&& count[icmPeOp_matrix] == 0 && count[icmPeOp_cLUT] == 0
&& count[icmPeOp_fmt] == 0 && count[icmPeOp_complex] == 0) {
p->attr.op = icmPeOp_NOP;
/* If there is nothing other than perch, label it as perch */
} else if (count[icmPeOp_perch] > 0
&& count[icmPeOp_matrix] == 0 && count[icmPeOp_cLUT] == 0
&& count[icmPeOp_fmt] == 0 && count[icmPeOp_complex] == 0) {
p->attr.op = icmPeOp_perch;
/* If there is one of matrix/clut/fmt + any number of NOP or per channel, */
/* label it as that op. */
} else if (count[icmPeOp_matrix] == 1 && count[icmPeOp_cLUT] == 0
&& count[icmPeOp_fmt] == 0 && count[icmPeOp_complex] == 0) {
p->attr.op = icmPeOp_matrix;
} else if (count[icmPeOp_matrix] == 0 && count[icmPeOp_cLUT] == 1
&& count[icmPeOp_fmt] == 0 && count[icmPeOp_complex] == 0) {
p->attr.op = icmPeOp_cLUT;
} else if (count[icmPeOp_matrix] == 0 && count[icmPeOp_cLUT] == 0
&& count[icmPeOp_fmt] == 1 && count[icmPeOp_complex] == 0) {
p->attr.op = icmPeOp_fmt;
/* else label as complex */
} else {
p->attr.op = icmPeOp_complex;
}
return ICM_ERR_OK;
}
/* ---------------------------------------------------------- */
/* A set of N x 1d Curves */
/* (icSigLut8Type, icSigLut16Type) */
static void (*icmPeCurveSet_serialise)(icmPeCurveSet *p, icmFBuf *b) = NULL;
/* Serialise this tag type for a Lut8 or Lut 16 */
static void icmPeCurveSet_LUT816_serialise(icmPeCurveSet *p, icmFBuf *b) {
icmPeCurve **pe = (icmPeCurve **)p->pe;
unsigned int n;
for (n = 0; n < p->inputChan; n++) {
icmSn_PeSubTag(b, NULL, NULL, &p->pe[n], p->ttype, p->rdff, p->dp);
}
// Can't ICMRDCHECKCONSUMED(icmPeCurveSet) because there is no directory above us
}
static void (*icmPeCurveSet_dump)(icmPeCurveSet *p, icmFile *op, int verb) = NULL;
/* Dump a human readable description */
static void icmPeCurveSet_LUT816_dump(icmPeCurveSet *p, icmFile *op, int verb) {
int pad = p->dp;
if (verb <= 0)
return;
if (verb >= 1) {
icmPeCurve **pe = (icmPeCurve **)p->pe;
unsigned int i, j;
for (i = 0; i < pe[0]->count; i++) {
op->printf(op,PAD(" %3u: "),i);
for (j = 0; j < p->inputChan; j++)
op->printf(op," %1.10f",pe[j]->data[i]);
op->printf(op,"\n");
}
}
}
static int icmPeCurveSet_check(icmPeCurveSet *p, icTagSignature sig, int rd) {
icc *icp = p->icp;
unsigned int n;
/* Check no. of in/out channels */
if (p->inputChan != p->outputChan)
icmFormatWarning(icp, ICM_FMTF_CSET_CHAN, "icmPeCurveSet input/output channels %u %u mismatch",p->inputChan , p->outputChan);
/* If Lut8 or Lut16, check that all the sub-tags are icmSig816Curve, that they */
/* are all icmCurveSpec and that they all have the same table size */
if (p->ttype == icmSig816Curves) {
for (n = 0; n < p->inputChan; n++) {
if (p->pe[n] == NULL)
continue; /* Hmm. */
if (p->pe[n]->ttype != icmSig816Curve)
icmFormatWarning(icp, ICM_FMT_CSET_SUBT, "icmPeCurveSet sub-tag %u is not icmSig816Curve",n);
else {
icmPeCurve **pe = (icmPeCurve **)p->pe;
if (pe[n]->ctype != icmCurveSpec)
icmFormatWarning(icp, ICM_FMT_CURV_CTYPE, "icmPeCurveSet sub-tag %u is not CurveSpec",n);
if (pe[n]->count != pe[0]->count)
icmFormatWarning(icp, ICM_FMT_CURV_POIMATCH, "icmPeCurveSet sub-tag %u count %u doesn't match (should be %u)",n,pe[n]->count, pe[0]->count);
}
}
}
/* Check all sub-icmPe's */
for (n = 0; n < p->inputChan; n++) {
if (p->pe[n] == NULL)
continue; /* Hmm. */
p->pe[n]->check(p->pe[n], sig, rd);
if (p->icp->e.c != ICM_ERR_OK)
return p->icp->e.c;
}
return p->icp->e.c;
}
/* Compare another with this */
static int icmPeCurveSet_cmp(icmPeCurveSet *dst, icmPeCurveSet *src) {
unsigned int i;
if (dst->ttype != src->ttype)
return 1;
if (dst->inputChan != src->inputChan
|| dst->outputChan != src->outputChan)
return 1;
for (i = 0; i < dst->inputChan; i++) {
if ((dst->pe[i] == NULL && src->pe[i] != NULL)
|| (dst->pe[i] != NULL && src->pe[i] == NULL))
return 1;
if (dst->pe[i] != NULL && src->pe[i] != NULL) {
if (dst->pe[i]->cmp == NULL) {
icm_err(dst->icp,ICM_ERR_UNIMP_TTYPE_CMP,"icmPeCurveSet_cmp: unimplemented for %s",
icmTypeSig2str(dst->pe[i]->ttype));
return 1;
}
}
if (dst->pe[i]->cmp(dst->pe[i], src->pe[i]))
return 1;
}
return 0;
}
/* Copy or translate from source to this ttype */
static int icmPeCurveSet_cpy(icmPeCurveSet *dst, icmBase *isrc) {
icc *p = dst->icp;
if (dst->etype == icmSigPeCurveSet
&& isrc->etype == icmSigPeCurveSet) {
icmPeCurveSet *src = (icmPeCurveSet *)isrc;
unsigned int i;
/* Free any existing sub-objects */
for (i = 0; i < dst->inputChan; i++) {
if (dst->pe[i] != NULL)
dst->pe[i]->del(dst->pe[i]);
}
dst->inputChan = src->inputChan;
dst->outputChan = src->outputChan;
for (i = 0; i < dst->inputChan; i++) {
if ((dst->pe[i] = icc_new_pe_imp(p, src->pe[i]->ttype, dst->ttype, dst->rdff)) == NULL)
return p->e.c;
dst->pe[i]->cpy(dst->pe[i], (icmBase *)src->pe[i]);
}
return ICM_ERR_OK;
}
return icm_err(dst->icp, ICM_ERR_UNIMP_TTYPE_COPY,"icmPeCurveSet_cpy: unimplemented tagtype");
}
/* Setup attr */
static int icmPeCurveSet_init(icmPeCurveSet *p) {
unsigned int i;
p->attr.op = icmPeOp_NOP;
p->attr.fwd = 1;
p->attr.bwd = 1;
for (i = 0; i < p->inputChan; i++) {
if (p->pe[i] != NULL) {
p->pe[i]->init(p->pe[i]);
/* This is a NOP if all channels are NOPs */
if (p->pe[i]->attr.op != icmPeOp_NOP)
p->attr.op = icmPeOp_perch;
p->attr.fwd &= p->pe[i]->attr.fwd;
p->attr.bwd &= p->pe[i]->attr.bwd;
}
}
return ICM_ERR_OK;
}
/* Polymorphic constructor. */
/* Create an empty object. Return null on error */
/* Set ttype to the serialisation type of this Pe or its */
/* parent ttype if it doesn't use a sig in its serialisation. */
/* Use ttype = icmSigUnknownType if not being serialised. */
static icmBase *new_icmPeCurveSet(icc *icp, icTagTypeSignature ttype) {
ICM_PE_ALLOCINIT(icmPeCurveSet, icmSigPeCurveSet, ttype)
if (p->ttype == icmSig816Curves) { /* icmLut8 and icmLut16 curves */
ICM_PE_SETATTR(p->attr, 1, 0, 0, icmPeOp_perch, 1, 1)
p->serialise = icmPeCurveSet_LUT816_serialise;
p->dump = icmPeCurveSet_LUT816_dump;
} else {
icm_err(p->icp, ICM_ERR_PE_UNKNOWN_TTYPE, "new_icmPeCurveSet: Unknown ttype %s",icmtag2str(p->ttype));
p->icp->al->free(p->icp->al, p);
return NULL;
}
p->init = icmPeCurveSet_init;
p->lookup_fwd = icmPeCurveSet_lookup_fwd;
p->lookup_bwd = icmPeCurveSet_lookup_bwd;
p->cmp = icmPeCurveSet_cmp;
p->cpy = icmPeCurveSet_cpy;
return (icmBase *)p;
}
/* ---------------------------------------------------------- */
/* A linear/gamma/table/sampled curve */
/* (TRCTag, icSigLut8Type, icSigLut16Type) */
static void (*icmPeCurve_serialise)(icmPeCurve *p, icmFBuf *b) = NULL;
/* Serialise this tag type (embedded within TTYPE) */
static void icmPeCurve_TTYPE_serialise(icmPeCurve *p, icmFBuf *b) {
unsigned int i;
if (b->op == icmSnResize)
p->inited = 0;
/* Decode flag set by user into count */
if (b->op == icmSnResize) {
switch (p->ctype) {
case icmCurveLin:
p->count = 0;
break;
case icmCurveGamma:
p->count = 1;
break;
case icmCurveSpec:
/* User sets count for icmCurveSpec */
break;
default:
icm_err(p->icp, ICM_ERR_BADCURVE, "Unknown curve flag %d",p->ctype);
break;
}
}
icmSn_TagTypeSig32(b, &p->ttype); /* 0-3: Curve Tag Type signature */
icmSn_pad(b, 4); /* 4-7: Zero padding */
icmSn_ui_UInt32(b, &p->count); /* 8-11: Number of parameters/table values */
/* decode count read into flag */
if (b->op == icmSnRead) {
if (p->count == 0)
p->ctype = icmCurveLin;
else if (p->count == 1)
p->ctype = icmCurveGamma;
else
p->ctype = icmCurveSpec;
}
if (icmArrayRdAllocResize(b, icmAResizeByCount, &p->_count, &p->count,
(void **)&p->data, sizeof(double), UINT_MAX, 0, 2, "icmCurve"))
return;
if (b->op & icmSnSerialise) { /* (Optimise for speed) */
if (p->count == 1) {
icmSn_d_U8Fix8(b, &p->data[0]); /* 12-13: Gamma value */
} else {
for (i = 0; i < p->count; i++)
icmSn_d_NFix16(b, &p->data[i]); /* 12 + 2 * n: Curve value */
}
}
ICMSNFREEARRAY(b, p->_count, p->data)
if (!p->emb) { /* Can check if first class tag, else not because there is no dir. above us */
ICMRDCHECKCONSUMED(icmPeCurve)
}
if (b->op == icmSnRead)
icmPeCurve_init(p);
else if (b->op == icmSnFree)
icmPeCurve_deinit(p);
}
/* Serialise this tag type for a Lut8 or Luut 16 */
static void icmPeCurve_LUT816_serialise(icmPeCurve *p, icmFBuf *b) {
if (b->op == icmSnResize)
p->inited = 0;
if (icmArrayRdAllocResize(b, icmAResizeByCount, &p->_count, &p->count,
(void **)&p->data, sizeof(double),
UINT_MAX, 0, p->bpv, "icmLut8/16"))
return;
if (b->op & icmSnSerialise) {
unsigned int i;
if (p->bpv == 1) {
for (i = 0; i < p->count; i++)
icmSn_d_NFix8(b, &p->data[i]);
} else {
for (i = 0; i < p->count; i++)
icmSn_d_NFix16(b, &p->data[i]);
}
}
ICMSNFREEARRAY(b, p->_count, p->data)
// ICMRDCHECKCONSUMED(icmPeCurve) can't because there's no directory above us
if (b->op == icmSnRead)
icmPeCurve_init(p);
else if (b->op == icmSnFree)
icmPeCurve_deinit(p);
}
/* Dump a human readable description */
static void icmPeCurve_dump(icmPeCurve *p, icmFile *op, int verb) {
unsigned int n;
int pad = p->dp;
op->printf(op,PAD("Curve:\n"));
if (p->ctype == icmCurveLin) {
op->printf(op,PAD(" Curve is linear\n"));
} else if (p->ctype == icmCurveGamma) {
op->printf(op,PAD(" Curve is gamma of %1.10f\n"),p->data[0]);
} else if (p->ctype == icmCurveSpec) {
{
op->printf(op,PAD(" No. elements = %u\n"),p->count);
if (verb >= 2) {
for (n = 0; n < p->count; n++)
op->printf(op,PAD(" %3lu: %1.10f\n"),n,p->data[n]);
}
}
} else {
op->printf(op,PAD(" Curve has unknown ctype %u\n"),p->ctype);
}
}
static int icmPeCurve_check(icmPeCurve *p, icTagSignature sig, int rd) {
icc *icp = p->icp;
unsigned int n;
/* Check no. of in/out channels */
if (p->inputChan != 1
|| p->outputChan != 1)
icmFormatWarning(icp, ICM_FMT_CURV_CHAN, "icmPeCurve input/output channels not = 1 (are %u, %u)",p->inputChan , p->outputChan);
if (p->ttype == icSigCurveType) {
if (p->ctype == icmCurveSpec && p->count < 2) {
icmFormatWarning(p->icp, ICM_FMT_CURV_POINTS,
"icmCurve count %u < 2",p->count);
}
}
return p->icp->e.c;
}
/* Compare another with this */
static int icmPeCurve_cmp(icmPeCurve *dst, icmPeCurve *src) {
unsigned int np, i;
if (dst->ttype != src->ttype)
return 1;
if (dst->inputChan != src->inputChan
|| dst->outputChan != src->outputChan)
return 1;
if (dst->ctype != src->ctype)
return 1;
if (dst->count != src->count)
return 1;
for (i = 0; i < dst->count; i++) {
if (dst->data[i] != src->data[i])
return 1;
}
return 0;
}
/* Copy or translate from source to this ttype */
static int icmPeCurve_cpy(icmPeCurve *dst, icmBase *isrc) {
icc *p = dst->icp;
if (dst->etype == icmSigPeCurve
&& isrc->etype == icmSigPeCurve) {
icmPeCurve *src = (icmPeCurve *)isrc;
unsigned int np, i;
dst->inputChan = src->inputChan;
dst->outputChan = src->outputChan;
dst->ctype = src->ctype;
dst->count = src->count;
dst->allocate(dst);
for (i = 0; i < src->count; i++) {
dst->data[i] = src->data[i];
}
return ICM_ERR_OK;
}
return icm_err(dst->icp, ICM_ERR_UNIMP_TTYPE_COPY,"icmPeCurve_cpy: unimplemented tagtype");
}
/* Polymorphic constructor. */
/* Create an empty PeCurve object. Return null on error */
static icmBase *new_icmPeCurve(icc *icp, icTagTypeSignature ttype) {
ICM_PE_ALLOCINIT(icmPeCurve, icmSigPeCurve, ttype)
p->inputChan = p->outputChan = 1;
if (p->ttype == icSigCurveType) {
ICM_PE_SETATTR(p->attr, 0, 0, 0, icmPeOp_perch, 1, 1)
p->ctype = icmCurveUndef;
p->serialise = icmPeCurve_TTYPE_serialise;
} else if (p->ttype == icmSig816Curve) {
ICM_PE_SETATTR(p->attr, 0, 0, 0, icmPeOp_perch, 1, 1)
p->ctype = icmCurveSpec;
p->serialise = icmPeCurve_LUT816_serialise;
} else {
icm_err(p->icp, ICM_ERR_PE_UNKNOWN_TTYPE, "new_icmPeCurve: Unknown ttype %s",icmtag2str(p->ttype));
p->icp->al->free(p->icp->al, p);
return NULL;
}
p->init = icmPeCurve_init;
p->cmp = icmPeCurve_cmp;
p->cpy = icmPeCurve_cpy;
p->lookup_fwd = icmPeCurve_lookup_fwd;
p->lookup_bwd = icmPeCurve_lookup_bwd;
return (icmBase *)p;
}
/* ---------------------------------------------------------- */
/* An N x M + F matrix */
/* (icSigLut8Type, icSigLut16Type) */
static void (*icmPeMatrix_serialise)(icmPeMatrix *p, icmFBuf *b) = NULL;
/* Serialise this from/to a Lut8 or Lut16 Matrix */
void icmPeMatrix_LUT816_serialise(icmPeMatrix *p, icmFBuf *b) {
unsigned int m, n;
if (b->op == icmSnResize)
p->inited = 0;
if (b->op & icmSnSerialise) {
for (m = 0; m < 3; m++) { /* Rows */
for (n = 0; n < 3; n++) /* Columns */
icmSn_d_S15Fix16(b, &p->mx[m][n]);
}
}
/* Create inverse and flags */
if (b->op == icmSnRead) {
for (m = 0; m < p->outputChan; m++)
p->ct[m] = 0.0;
icmPeMatrix_init(p);
}
// Can't ICMRDCHECKCONSUMED(icmPeMatrix) because there is no directory above us
}
/* Dump a human readable description */
static void icmPeMatrix_dump(icmPeMatrix *p, icmFile *op, int verb) {
int pad = p->dp;
unsigned int m, n;
if (verb <= 0)
return;
if (verb >= 1) {
{
for (m = 0; m < 3; m++) {
op->printf(op,PAD(" "));
for (n = 0; n < 3; n++)
op->printf(op,"%1.10f%s",p->mx[m][n], n < (p->inputChan-1) ? ", " : "");
op->printf(op,"\n");
}
}
}
}
static int icmPeMatrix_check(icmPeMatrix *p, icTagSignature sig, int rd) {
icc *icp = p->icp;
unsigned int n;
/* Check inChan == outChan == 3 and ct[] == 0.0 */
if (p->ttype == icmSig816Matrix) {
unsigned int n;
if (p->inputChan != 3 || p->outputChan != 3)
icmFormatWarning(icp, ICM_FMT_MATX_CHAN, "icmSig816Matrix input/output channels not = 3 (are %u, %u)",p->inputChan , p->outputChan);
for (n = 0; n < p->outputChan; n++) {
if (p->ct[n] != 0.0)
icmFormatWarning(icp, ICM_FMT_MATX_CNST, "icmSig816Matrix constant %u is not 0.0 (is %f)",n , p->ct[n]);
}
}
return p->icp->e.c;
}
/* Compare another with this */
static int icmPeMatrix_cmp(icmPeMatrix *dst, icmPeMatrix *src) {
unsigned int m, n;
if (dst->ttype != src->ttype)
return 1;
if (dst->inputChan != src->inputChan
|| dst->outputChan != src->outputChan)
return 1;
for (m = 0; m < dst->outputChan; m++) {
for (n = 0; n < dst->inputChan; n++) {
if (dst->mx[m][n] != src->mx[m][n])
return 1;
}
}
for (m = 0; m < dst->outputChan; m++) {
if (dst->ct[m] != src->ct[m])
return 1;
}
return 0;
}
/* Copy or translate from source to this ttype */
static int icmPeMatrix_cpy(icmPeMatrix *dst, icmBase *isrc) {
icc *p = dst->icp;
if (dst->etype == icmSigPeMatrix
&& isrc->etype == icmSigPeMatrix) {
icmPeMatrix *src = (icmPeMatrix *)isrc;
unsigned int m, n;
dst->inputChan = src->inputChan;
dst->outputChan = src->outputChan;
for (m = 0; m < src->outputChan; m++) {
for (n = 0; n < src->inputChan; n++)
dst->mx[m][n] = src->mx[m][n];
}
for (m = 0; m < src->outputChan; m++)
dst->ct[m] = src->ct[m];
return ICM_ERR_OK;
}
return icm_err(dst->icp, ICM_ERR_UNIMP_TTYPE_COPY,"icmPeMatrix_cpy: unimplemented tagtype");
}
/* Polymorphic constructor. */
/* Create an empty object. Return null on error */
static icmBase *new_icmPeMatrix(icc *icp, icTagTypeSignature ttype) {
unsigned int m;
ICM_PE_ALLOCINIT(icmPeMatrix, icmSigPeMatrix, ttype)
/* Assume NOP until icmPeMatrix_init is called */
if (p->ttype == icmSig816Matrix) {
ICM_PE_SETATTR(p->attr, 0, 0, 0, icmPeOp_NOP, 1, 1)
p->inputChan = p->outputChan = 3;
p->serialise = icmPeMatrix_LUT816_serialise;
} else {
icm_err(p->icp, ICM_ERR_PE_UNKNOWN_TTYPE, "new_icmPeMatrix: Unknown ttype %s",icmtag2str(p->ttype));
p->icp->al->free(p->icp->al, p);
return NULL;
}
/* Default to a NOP matrix */
for (m = 0; m < (p->outputChan < p->inputChan ? p->outputChan: p->inputChan); m++)
p->mx[m][m] = 1.0;
for (m = 0; m < p->outputChan; m++)
p->ct[m] = 0.0;
p->init = icmPeMatrix_init;
p->cmp = icmPeMatrix_cmp;
p->cpy = icmPeMatrix_cpy;
p->lookup_fwd = icmPeMatrix_lookup_fwd;
p->lookup_bwd = icmPeMatrix_lookup_bwd;
return (icmBase *)p;
}
/* ---------------------------------------------------------- */
/* An N x M cLUT */
/* (icSigLut8Type, icSigLut16Type) */
static void (*icmPeClut_serialise)(icmPeClut *p, icmFBuf *b) = NULL;
/* Serialise this from/to a Lut8 or Lut16 */
void icmPeClut_LUT816_serialise(icmPeClut *p, icmFBuf *b) {
int ind;
unsigned int i;
unsigned int clutsize;
if (b->op == icmSnResize)
p->inited = 0;
/* Clut table */
/* Grid resolution table */
ind = 0;
clutsize = 1;
for (i = 0; i < p->inputChan; i++)
clutsize = sati_mul(&ind, p->clutPoints[i], clutsize);
clutsize = sati_mul(&ind, p->outputChan, clutsize);
if (ind != 0) {
icmFmtWarn(b, ICM_FMTF_CLUT_LUTSIZE, "icmPeClut table size overflow");
return;
}
if (icmArrayRdAllocResize(b, icmAResizeByCount, &p->_clutsize, &clutsize,
(void **)&p->clutTable, sizeof(double),
UINT_MAX, 0, p->bpv, "icmLut8/16"))
return;
if (b->op & icmSnSerialise) {
if (p->bpv == 1) {
for (i = 0; i < clutsize; i++)
icmSn_d_NFix8(b, &p->clutTable[i]);
} else {
for (i = 0; i < clutsize; i++)
icmSn_d_NFix16(b, &p->clutTable[i]);
}
}
ICMSNFREEARRAY(p, p->_clutsize, p->clutTable)
// Can't ICMRDCHECKCONSUMED(icmPeClut) because there is no directory above us
if (b->op & icmSnAlloc) { /* After alloc or read */
icmPeClut_init(p);
}
}
/* Dump a human readable description */
static void icmPeClut_dump(icmPeClut *p, icmFile *op, int verb) {
int pad = p->dp;
unsigned int i, j;
if (verb <= 0)
return;
if (verb >= 2) {
unsigned int ii[MAX_CHAN]; /* Input channel index */
for (j = 0; j < p->inputChan; j++)
ii[j] = 0;
for (i = 0; i < p->_clutsize;) {
unsigned int k;
/* Print table entry index */
op->printf(op,PAD(" "));
for (j = p->inputChan; j-- > 0;)
op->printf(op," %2u",ii[j]);
op->printf(op,":");
/* Print table entry contents */
for (k = 0; k < p->outputChan; k++, i++)
op->printf(op," %1.10f",p->clutTable[i]);
op->printf(op,"\n");
/* Increment index */
for (j = 0; j < p->inputChan; j++) {
ii[j]++;
if (ii[j] < p->clutPoints[j])
break; /* No carry */
ii[j] = 0;
}
}
}
}
static int icmPeClut_check(icmPeClut *p, icTagSignature sig, int rd) {
icc *icp = p->icp;
unsigned int n;
/* Check all clutPoints[] are >= 2 */
for (n = 0; n < p->inputChan; n++) {
if (p->clutPoints[n] < 2)
icmFormatWarning(icp, ICM_FMT_CLUT_RES, "icmPeClut constant %u resolution < 2 (is %u)",n , p->clutPoints[n]);
}
return p->icp->e.c;
}
/* Compare another with this */
static int icmPeClut_cmp(icmPeClut *dst, icmPeClut *src) {
unsigned int np, i;
if (dst->ttype != src->ttype)
return 1;
if (dst->inputChan != src->inputChan
|| dst->outputChan != src->outputChan)
return 1;
for (i = 0; i < dst->inputChan; i++) {
if (dst->clutPoints[i] != src->clutPoints[i])
return 1;
}
if (dst->_clutsize != src->_clutsize)
return 1;
for (i = 0; i < dst->_clutsize; i++) {
if (dst->clutTable[i] != src->clutTable[i])
return 1;
}
return 0;
}
/* Copy or translate from source to this ttype */
static int icmPeClut_cpy(icmPeClut *dst, icmBase *isrc) {
icc *p = dst->icp;
if (dst->etype == icmSigPeClut
&& isrc->etype == icmSigPeClut) {
icmPeClut *src = (icmPeClut *)isrc;
unsigned int i;
dst->inputChan = src->inputChan;
dst->outputChan = src->outputChan;
for (i = 0; i < src->inputChan; i++)
dst->clutPoints[i] = src->clutPoints[i];
dst->allocate(dst);
for (i = 0; i < dst->_clutsize; i++)
dst->clutTable[i] = src->clutTable[i];
return ICM_ERR_OK;
}
return icm_err(dst->icp, ICM_ERR_UNIMP_TTYPE_COPY,"icmPeClut_cpy: unimplemented tagtype");
}
/* Polymorphic constructor. */
/* Create an empty object. Return null on error */
static icmBase *new_icmPeClut(icc *icp, icTagTypeSignature ttype) {
ICM_PE_ALLOCINIT(icmPeClut, icmSigPeClut, ttype)
if (p->ttype == icmSig816CLUT) {
ICM_PE_SETATTR(p->attr, 0, 0, 0, icmPeOp_cLUT, 1, 0)
p->serialise = icmPeClut_LUT816_serialise;
} else {
icm_err(p->icp, ICM_ERR_PE_UNKNOWN_TTYPE, "new_icmPeClut: Unknown ttype %s",icmtag2str(p->ttype));
p->icp->al->free(p->icp->al, p);
return NULL;
}
p->use_sx = 1; /* Default */
p->init = icmPeClut_init;
p->cmp = icmPeClut_cmp;
p->cpy = icmPeClut_cpy;
p->lookup_fwd = icmPeClut_lookup_fwd;
p->lookup_bwd = icmPeClut_lookup_bwd;
p->min_max = icmPeClut_min_max;
p->choose_alg = icmPeClut_choose_alg;
p->get_tac = icmPeClut_get_tac;
return (icmBase *)p;
}
/* ============================================================ */
/* Table of icmPe Types and constructor, plus */
/* valid ttypes that they can be serialised as. */
static struct {
icmPeSignature pesig; /* The icmPe signature */
struct _icmBase *(*new_obj)(struct _icc *icp, icTagTypeSignature ttype);
icTagTypeSignature ttypes[6]; /* Known ttypes */
} icmPeConstrTable[] = {
{ icmSigPeCurve, new_icmPeCurve,
{icSigCurveType,
icMaxEnumTagType} },
{ icmSigPeMatrix, new_icmPeMatrix,
{
icSigLut8Type, icSigLut16Type, icMaxEnumTagType} },
{ icmSigPeClut, new_icmPeClut,
{
icSigLut8Type, icSigLut16Type, icMaxEnumTagType} },
{ icmPeMaxEnum }
};
/* Create an empty icmPe object. Return null on error */
/* Set ttype to the serialisation type of this Pe or its */
/* parent ttype if it doesn't use a sig in its serialisation. */
/* Use ttype = icmSigUnknownType if not being serialised. */
/* (Used just to construct transforms ?) */
static icmPe *new_icmPe(
icc *p,
icTagTypeSignature ttype, /* Determines serialisation & dump type. */
icmPeSignature pesig /* Type of icmPe */
) {
icmPe *nob;
unsigned int j, k;
/* Find the table entry for this pesig */
for (j = 0; icmPeConstrTable[j].pesig != pesig
&& icmPeConstrTable[j].pesig != icmPeMaxEnum; j++)
;
if (icmPeConstrTable[j].pesig == icmPeMaxEnum) { /* Unknown pesig */
icm_err(p, ICM_ERR_PE_NOT_KNOWN, "new_icmPe: sig '%s' is not known", icmtag2str(pesig));
return NULL;
}
/* Check that the ttype is known */
if (ttype != icmSigUnknownType) {
for (k = 0; icmPeConstrTable[j].ttypes[k] != icMaxEnumTagType; k++) {
if (icmPeConstrTable[j].ttypes[k] == ttype)
break;
}
if (icmPeConstrTable[j].ttypes[k] == icMaxEnumTagType) {
icmFormatWarning(p, ICM_ERR_PE_UNKNOWN_TTYPE,
"new_icmPe: icmPe %s has unknown ttype %s\n",
icmPeSig2str(pesig), icmTypeSig2str(ttype));
}
}
/* Allocate the empty object */
if ((nob = (icmPe *)icmPeConstrTable[j].new_obj(p, ttype)) == NULL)
return NULL;
return nob;
}
/* ------------------------------------------------------------ */
/* Lut8Type, Lut16type */
/* Serialise this tag type */
static void icmLut1_serialise(icmLut1 *p, icmFBuf *b) {
static icTagTypeSignature pettypes[4] = {
icmSig816Matrix, icmSig816Curves, icmSig816CLUT, icmSig816Curves
};
icc *icp = b->icp;
unsigned int n;
if (b->op & icmSnSerialise) {
unsigned int clutpoints;
/* Special case */
if (b->op == icmSnWrite) {
if (icp->allowclutPoints256 && p->clutPoints == 256)
clutpoints = 0;
else
clutpoints = p->clutPoints;
}
/* Setup tag header */
icmSn_TagTypeSig32(b, &p->ttype); /* 0-3: Lut Tag Type signature */
icmSn_pad(b, 4); /* 4-7: Zero padding */
icmSn_check_ui_UInt8(b, &p->inputChan, MAX_CHAN); /* 8: Number of input channels */
icmSn_check_ui_UInt8(b, &p->outputChan, MAX_CHAN); /* 9: Number of output channels */
icmSn_ui_UInt8(b, &clutpoints); /* 10: Clut resolution */
icmSn_pad(b, 1); /* 11: Padding, must be 0 */
if (b->op == icmSnRead) {
p->bpv = (p->ttype == icSigLut8Type) ? 1 : 2; /* Recompute after read*/
/* Special case */
if (icp->allowclutPoints256 && clutpoints == 0)
p->clutPoints = 256;
else
p->clutPoints = clutpoints;
}
}
/* Serialise the Elements */
for (n = 0; n < 4; n++) {
if (icp->e.c != ICM_ERR_OK) {
return;
}
/* Before input table */
if (n == icmLut816_ix_in) {
if (p->bpv == 2) {
icmSn_ui_UInt16(b, &p->inputEnt); /* 48-49: Input table entries */
icmSn_ui_UInt16(b, &p->outputEnt); /* 50-51: Output table entries */
}
}
if (b->op & icmSnAlloc) {
icTagTypeSignature tsig;
if (p->pe[n] == NULL
&& (p->pe[n] = icc_new_pe_imp(icp, pettypes[n], p->ttype, p->rdff)) == NULL) {
return;
}
/* Set element in/out channels - changeover from in to out at icmLut816_ix_CLUT */
/* Leave matrix as hard coded 3x3 */
if (pettypes[n] != icmSig816Matrix) {
p->pe[n]->inputChan = n <= 2 ? p->inputChan : p->outputChan;
p->pe[n]->outputChan = n <= 1 ? p->inputChan : p->outputChan;
}
/* We can't use sub-elements to allocation, we have to */
/* pre-allocate here... */
tsig = pettypes[n];
/* If we can, add curves to CurveSet */
if (tsig == icmSig816Curves && p->pe[n]->inputChan > 0) {
icmPeCurveSet *pe = (icmPeCurveSet *)p->pe[n];
unsigned int m;
pe->bpv = p->bpv;
for (m = 0; m < pe->inputChan; m++) {
icmPeCurve *cpe;
if (pe->pe[m] == NULL
&& (pe->pe[m] = icc_new_pe_imp(icp, icmSig816Curve, pe->ttype, p->rdff))
== NULL) {
icm_err(icp, ICM_ERR_NEW_PE_FAILED,"icmLut1_serialise: icc_new_pe_imp()"
" for %s failed",icmtag2str(icSigCurveType));
return;
}
cpe = (icmPeCurve *)pe->pe[m];
cpe->bpv = p->bpv;
cpe->ctype = icmCurveSpec;
cpe->count = (n == icmLut816_ix_in) ? p->inputEnt : p->outputEnt;
cpe->allocate(cpe);
}
/* We don't have to allocate matrix contents.. */
} else if (pettypes[n] == icmSig816Matrix && p->pe[n]->inputChan > 0) {
/* Allocate cLUT array */
} else if (pettypes[n] == icmSig816CLUT
&& p->pe[n]->inputChan > 0 && p->pe[n]->outputChan > 0) {
icmPeClut *pe = (icmPeClut *)p->pe[n];
unsigned int m;
pe->bpv = p->bpv;
for (m = 0; m < pe->inputChan; m++) {
pe->clutPoints[m] = p->clutPoints;
if (pe->clutPoints[m] == 0)
break;
}
if (m >= pe->inputChan) {
pe->allocate(pe);
}
}
/* Setup typed aliases to Processing Elements */
p->pe_mx = (icmPeMatrix *)p->pe[icmLut816_ix_Matrix];
if (p->pe[icmLut816_ix_in] != NULL)
p->pe_ic = (icmPeCurve **)((icmPeCurveSet *)p->pe[icmLut816_ix_in])->pe;
else
p->pe_ic = NULL;
p->pe_cl = (icmPeClut *)p->pe[icmLut816_ix_CLUT];
if (p->pe[icmLut816_ix_out] != NULL)
p->pe_oc = (icmPeCurve **)((icmPeCurveSet *)p->pe[icmLut816_ix_out])->pe;
else
p->pe_oc = NULL;
}
/* Serialize a sub-tag */
icmSn_PeSubTag(b, NULL, NULL, &p->pe[n], p->ttype, p->rdff, p->dp);
}
ICMSNFREEARRAY(b, p->_count, p->pe)
ICMRDCHECKCONSUMED(icmLut1)
}
/* Dump a text description of the object */
static void icmLut1_dump(
icmLut1 *p,
icmFile *op, /* Output to dump to */
int verb /* Verbosity level */
) {
int pad = p->dp;
if (verb <= 0)
return;
if (p->ttype == icSigLut8Type) {
op->printf(op,"Lut8:\n");
} else {
op->printf(op,"Lut16:\n");
}
op->printf(op," Input Channels = %u\n",p->inputChan);
op->printf(op," Output Channels = %u\n",p->outputChan);
op->printf(op," CLUT resolution = %u\n",p->clutPoints);
op->printf(op," Input Table entries = %u\n",p->inputEnt);
op->printf(op," Output Table entries = %u\n",p->outputEnt);
if (verb >= 2) {
op->printf(op," XYZ matrix:\n");
if (p->pe[icmLut816_ix_Matrix] != NULL)
p->pe[icmLut816_ix_Matrix]->dump(p->pe[icmLut816_ix_Matrix], op, verb);
op->printf(op," Input table:\n");
if (p->pe[icmLut816_ix_in] != NULL)
p->pe[icmLut816_ix_in]->dump(p->pe[icmLut816_ix_in], op, verb);
op->printf(op," CLUT table:\n");
if (p->pe[icmLut816_ix_CLUT] != NULL)
p->pe[icmLut816_ix_CLUT]->dump(p->pe[icmLut816_ix_CLUT], op, verb);
op->printf(op," Output table:\n");
if (p->pe[icmLut816_ix_out] != NULL)
p->pe[icmLut816_ix_out]->dump(p->pe[icmLut816_ix_out], op, verb);
}
}
/* Check Lut1 */
static int icmLut1_check(icmLut1 *p, icTagSignature sig, int rd) {
icc *icp = p->icp;
icColorSpaceSignature inputSpace, outputSpace;
unsigned int ichan, ochan;
unsigned int n;
int vpurp = 1;
switch (icp->get_tag_lut_purpose(icp, p->creatorsig)) {
case icmTPLutFwd: /* AtoBn: Device to PCS */
inputSpace = icp->header->colorSpace;
outputSpace = icp->header->pcs;
break;
case icmTPLutBwd: /* BtoAn: PCS to Device */
inputSpace = icp->header->pcs;
outputSpace = icp->header->colorSpace;
break;
case icmTPLutGamut: /* Gamut: PCS to Gray */
inputSpace = icp->header->pcs;
outputSpace = icSigGrayData;
break;
case icmTPLutPreview: /* Preview: PCS to PCS */
inputSpace = icp->header->pcs;
outputSpace = icp->header->pcs;
break;
default:
vpurp = 0;
icmFormatWarning(icp, ICM_FMT_LUPURP,
"icmLut1 Unknown LUT purpose");
}
if (vpurp) {
/* Does number of channels match header device space encoding ? */
ichan = icmCSSig2nchan(inputSpace);
if (p->inputChan != ichan) {
icmFormatWarning(icp, ICM_FMT_LUICHAN,
"icmLut1 no. input channels %d doesn't match colorspace %d",
p->inputChan,ichan);
}
ochan = icmCSSig2nchan(outputSpace);
if (p->outputChan != ochan) {
icmFormatWarning(icp, ICM_FMT_LUOCHAN,
"icmLut1 no. output channels %d doesn't match colorspace %d",
p->outputChan,ochan);
}
}
if (p->ttype == icSigLut8Type) {
if (p->inputEnt != 256 || p->outputEnt != 256) {
icmFormatWarning(icp, ICM_FMT_LU8IOENT,
"icmLut8 1D input or output tables don't have 256 entries");
}
} else if (p->inputEnt > 4096 || p->outputEnt > 4096) {
icmFormatWarning(icp, ICM_FMT_LUIOENT,
"icmLut8 1D input or output have no. entries > 4096");
}
/* Check all sub-icmPe's */
for (n = 0; n < 4; n++) {
if (p->pe[n] == NULL)
continue; /* Hmm. */
p->pe[n]->check(p->pe[n], sig, rd);
if (p->icp->e.c != ICM_ERR_OK)
return p->icp->e.c;
}
/* ~8 add check that input must be 3 channel if matrix is non-unity */
return p->icp->e.c;
}
/* Create an empty object. Return null on error */
static icmBase *new_icmLut1(icc *icp, icTagTypeSignature ttype
) {
ICM_PE_SEQ_ALLOCINIT(icmLut1, icmSigPeLut816, ttype)
p->dp = 2; /* Dump padding */
p->count = 4; /* Pre-allocate pe[] */
if (icmArrayResize(icp, &p->_count, &p->count,
(void **)&p->pe, sizeof(icmPe *), "icmLut1 array")) {
return NULL;
}
if (ttype == icSigLut8Type) {
p->bpv = 1;
p->inputEnt = 256; /* By definition */
p->outputEnt = 256; /* By definition */
} else {
p->bpv = 2;
}
return (icmBase *)p;
}
/* ------------------------------------------------------------ */
/* RGB matrix-shaper transform (Not serialisable) */
/* Output is relative XYZ */
/* Dump a human readable description */
static void icmShaperMatrix_dump(icmShaperMatrix *p, icmFile *op, int verb) {
int pad = p->dp;
unsigned int i;
op->printf(op,PAD("ShaperMatrix:\n"));
op->printf(op,PAD(" Input Channels = %u\n"),p->inputChan);
op->printf(op,PAD(" Output Channels = %u\n"),p->outputChan);
op->printf(op,PAD(" No. elements = %u\n"),p->count);
for (i = 0; i < p->count; i++) {
op->printf(op,PAD(" PeType = %s\n"),icmPeSig2str(p->pe[i]->etype));
}
}
/* Delete the container and all its contents */
static void icmShaperMatrix_del(icmShaperMatrix *p) {
if (p->refcount > 0 && --p->refcount == 0) {
unsigned int i;
/* TagTypes are owned by icc. */
/* We took a reference to Pe's, so release them... */
for (i = 0; i < p->count; i++) {
if (p->pe[i] != NULL)
p->pe[i]->del(p->pe[i]); /* Deletes if last reference */
}
ICMFREEARRAY(p->icp, p->_count, p->pe)
p->icp->al->free(p->icp->al, p);
}
}
/* Create an empty object. Return null on error. */
/* Doesn't set icc error if tags not present. */
static icmPeSeq *new_icmShaperMatrix(
icc *icp,
int invert /* If nz, setup inverse of shape-matrix */
) {
icmPeCurveSet *cs;
icmPeMatrix *mx;
ICM_PE_SEQ_NS_ALLOCINIT(icmShaperMatrix, icmSigPeShaperMatrix)
p->dp = 2; /* Dump padding */
p->count = 2; /* Pre-allocate pe[] */
if (icmArrayResize(icp, &p->_count, &p->count,
(void **)&p->pe, sizeof(icmPe *), "icmShaperMatrix array")) {
return NULL;
}
p->outputChan = p->inputChan = 3;
/* See if the color spaces are appropriate for the matrix type */
if (icmCSSig2nchan(icp->header->colorSpace) != 3
|| (icmCSSig2type(icp->header->colorSpace) & CSSigType_DEV) == 0
|| (icmCSSig2type(icp->header->pcs) & CSSigType_PCS) == 0) {
p->del(p);
return NULL;
}
/* Find the appropriate tags */
if ((p->redCurve = icp->read_tag(icp, icSigRedTRCTag)) == NULL
|| ( p->redCurve->ttype != icSigCurveType
)
|| (p->greenCurve = icp->read_tag(icp, icSigGreenTRCTag)) == NULL
|| ( p->greenCurve->ttype != icSigCurveType
)
|| (p->blueCurve = icp->read_tag(icp, icSigBlueTRCTag)) == NULL
|| ( p->blueCurve->ttype != icSigCurveType
)
|| (p->redColrnt = (icmXYZArray *)icp->read_tag(icp, icSigRedColorantTag)) == NULL
|| p->redColrnt->ttype != icSigXYZType || p->redColrnt->count < 1
|| (p->greenColrnt = (icmXYZArray *)icp->read_tag(icp, icSigGreenColorantTag)) == NULL
|| p->greenColrnt->ttype != icSigXYZType || p->greenColrnt->count < 1
|| (p->blueColrnt = (icmXYZArray *)icp->read_tag(icp, icSigBlueColorantTag)) == NULL
|| p->blueColrnt->ttype != icSigXYZType || p->blueColrnt->count < 1) {
p->del(p);
return NULL;
}
/* Create and add Pe's */
if ((p->pe[0] = icp->new_pe(icp, icmSig816Curves, icSigLut16Type)) == NULL) {
p->del(p);
return NULL;
}
cs = (icmPeCurveSet *)p->pe[0];
cs->outputChan = cs->inputChan = 3;
if (cs->allocate(cs)) { /* Allocate variable elements */
p->del(p);
return NULL;
}
cs->pe[0] = (icmPe *)p->redCurve->reference(p->redCurve);
cs->pe[1] = (icmPe *)p->greenCurve->reference(p->greenCurve);
cs->pe[2] = (icmPe *)p->blueCurve->reference(p->blueCurve);
/* Matrix */
if ((p->pe[1] = icp->new_pe(icp, icmSig816Matrix, icSigLut16Type)) == NULL) {
p->del(p);
return NULL;
}
mx = (icmPeMatrix *)p->pe[1];
/* Copy the matrix */
mx->outputChan = mx->inputChan = 3;
mx->mx[0][0] = p->redColrnt->data[0].X;
mx->mx[0][1] = p->greenColrnt->data[0].X;
mx->mx[0][2] = p->blueColrnt->data[0].X;
mx->mx[1][1] = p->greenColrnt->data[0].Y;
mx->mx[1][0] = p->redColrnt->data[0].Y;
mx->mx[1][2] = p->blueColrnt->data[0].Y;
mx->mx[2][1] = p->greenColrnt->data[0].Z;
mx->mx[2][0] = p->redColrnt->data[0].Z;
mx->mx[2][2] = p->blueColrnt->data[0].Z;
mx->ct[0] = mx->ct[1] = mx->ct[2] = 0.0;
/* Workaround for buggy Kodak RGB profiles. Their matrix values */
/* may be scaled to 100 rather than 1.0, and the colorant curves */
/* may be scaled by 0.5 */
if (icp->header->cmmId == icmstr2tag("KCMS")) {
int i, j, oc = 0;
for (i = 0; i < 3; i++)
for (j = 0; j < 3; j++)
if (mx->mx[i][j] > 5.0)
oc++;
if (oc > 4) { /* Looks like it */
if ((p->icp->cflags & icmCFlagAllowQuirks) != 0) {
// Fix it
for (i = 0; i < 3; i++)
for (j = 0; j < 3; j++)
mx->mx[i][j] /= 100.0;
p->icp->op = icmSnRead; /* Let icmQuirkWarning know direction */
icmQuirkWarning(icp, ICM_FMT_MATRIX_SCALE, 0, "Matrix profile values have wrong scale");
} else {
p->del(p);
icmFormatWarning(icp, ICM_FMT_MATRIX_SCALE, "Matrix profile values have wrong scale");
return NULL;
}
}
}
if (invert) { /* Make this the inverse transform */
/* inputChan & outputChan are the same, so don't have to swap... */
p->pe[0] = new_icmPeInverter(icp, (icmPe *)mx);
p->pe[1] = new_icmPeInverter(icp, (icmPe *)cs);
mx->del(mx); /* icmPeInverter takes a reference, so remove ours.. */
cs->del(cs);
}
return (icmPeSeq *)p;
}
/* ------------------------------------------------------------ */
/* Monochrome matrix-mono transform (Not serialisable) */
/* Dump a human readable description */
static void icmShaperMono_dump(icmShaperMono *p, icmFile *op, int verb) {
int pad = p->dp;
unsigned int i;
op->printf(op,PAD("ShaperMono:\n"));
op->printf(op,PAD(" Input Channels = %u\n"),p->inputChan);
op->printf(op,PAD(" Output Channels = %u\n"),p->outputChan);
op->printf(op,PAD(" No. elements = %u\n"),p->count);
for (i = 0; i < p->count; i++) {
op->printf(op,PAD(" PeType = %s\n"),icmPeSig2str(p->pe[i]->etype));
}
}
/* Delete the container and all its contents */
static void icmShaperMono_del(icmShaperMono *p) {
if (p->refcount > 0 && --p->refcount == 0) {
unsigned int i;
/* TagTypes are owned by icc. */
/* We took a reference to Pe's, so release them... */
for (i = 0; i < p->count; i++) {
if (p->pe[i] != NULL)
p->pe[i]->del(p->pe[i]); /* Deletes if last reference */
}
ICMFREEARRAY(p->icp, p->_count, p->pe)
p->icp->al->free(p->icp->al, p);
}
}
/* Create an empty object. Return null on error. */
/* Doesn't set icc error if tags not present. */
static icmPeSeq *new_icmShaperMono(
icc *icp,
int invert /* If nz, setup inverse of shape-mono */
) {
icmPe *cu, *mo;
ICM_PE_SEQ_NS_ALLOCINIT(icmShaperMono, icmSigPeShaperMono)
p->dp = 2; /* Dump padding */
p->count = 2; /* Pre-allocate pe[] */
if (icmArrayResize(icp, &p->_count, &p->count,
(void **)&p->pe, sizeof(icmPe *), "icmShaperMono array")) {
return NULL;
}
p->inputChan = 1;
p->outputChan = 3;
/* See if the color spaces are appropriate for the mono type */
if (icmCSSig2nchan(icp->header->colorSpace) != 1
|| (icmCSSig2type(icp->header->colorSpace) & CSSigType_DEV) == 0
|| (icmCSSig2type(icp->header->pcs) & CSSigType_PCS) == 0) {
p->del(p);
return NULL;
}
/* Find the appropriate tags */
if ((p->grayCurve = icp->read_tag(icp, icSigGrayTRCTag)) == NULL
|| ( p->grayCurve->ttype != icSigCurveType
)) {
p->del(p);
return NULL;
}
/* Curve */
p->pe[0] = cu = (icmPe *)p->grayCurve->reference(p->grayCurve);
/* Mono conversion to PCS */
if ((p->pe[1] = mo = (icmPe *)new_icmPeMono(icp)) == NULL) {
p->del(p);
return NULL;
}
if (invert) { /* Make this the inverse transform */
p->inputChan = 3;
p->outputChan = 1;
p->pe[0] = new_icmPeInverter(icp, (icmPe *)mo);
p->pe[1] = new_icmPeInverter(icp, (icmPe *)cu);
mo->del(mo); /* icmPeInverter takes a reference, so remove ours.. */
cu->del(cu);
}
return (icmPeSeq *)p;
}
/* ------------------------------------------------------------------------------ */
/* A Pe container that implements the monochrome to PCS conversion (Not serialisable) */
/* Output is relative PCS, i.e. XYZ or Lab */
/* Dump a human readable description */
static void icmPeMono_dump(icmPeMono *p, icmFile *op, int verb) {
int pad = p->dp;
op->printf(op,PAD("Mono:\n"));
// ~8 isLab, white point...
}
static icmPe_lurv icmPeMono_lookup_fwd(icmPeMono *p, double *out, double *in) {
double inv = in[0]; /* In case in & out are aliases */
if (p->icp->header->pcs == icSigLabData) {
out[0] = 100.0 * inv;
out[1] = 0.0 * inv;
out[2] = 0.0 * inv;
} else {
out[0] = inv * p->icp->header->illuminant.X;
out[1] = inv * p->icp->header->illuminant.Y;
out[2] = inv * p->icp->header->illuminant.Z;
}
return icmPe_lurv_OK;
}
static icmPe_lurv icmPeMono_lookup_bwd(icmPeMono *p, double *out, double *in) {
if (p->icp->header->pcs == icSigLabData) {
out[0] = in[0]/100.0; /* Prop of PCS L* */
} else {
out[0] = in[1]/p->icp->header->illuminant.Y; /* Prop of PCS Y */
}
return icmPe_lurv_OK;
}
/* Delete the container and all its contents */
static void icmPeMono_del(icmPeMono *p) {
if (p->refcount > 0 && --p->refcount == 0) {
p->icp->al->free(p->icp->al, p);
}
}
static icmPe *new_icmPeMono(
icc *icp
) {
ICM_PE_NS_ALLOCINIT(icmPeMono, icmSigPeMono)
p->inputChan = 1;
p->outputChan = 3;
ICM_PE_SETATTR(p->attr, 0, 0, 0, icmPeOp_matrix, 1, 1)
p->lookup_fwd = icmPeMono_lookup_fwd;
p->lookup_bwd = icmPeMono_lookup_bwd;
return (icmPe *)p;
}
/* ------------------------------------------------------------------------------ */
/* A Pe container that inverts another Pe (Not serialisable) */
/* [ Note that we can't directly modify a Pe to be its inverse */
/* because it may be shared via a reference. A way of getting rid */
/* of icmPeInverter would be to add a clone_invert() method to */
/* icSigCurveType, icSigParametricCurveType, icmPeCurveSet, icmPeMatrix & icmPeMono. ] */
/* Dump a human readable description */
static void icmPeInverter_dump(icmPeInverter *p, icmFile *op, int verb) {
int pad = p->dp;
op->printf(op,PAD("Inverter:\n"));
p->pe->dp = pad + 2;
p->pe->dump(p->pe, op, verb);
}
/* Delete the container and all its contents */
static void icmPeInverter_del(icmPeInverter *p) {
if (p->refcount > 0 && --p->refcount == 0) {
p->pe->del(p->pe); /* Deletes if last reference */
p->icp->al->free(p->icp->al, p);
}
}
static icmPe_lurv icmPeInverter_trace_lookup_fwd(icmPeInverter *p, double *out, double *in) {
icmPe_lurv rv = icmPe_lurv_OK;
int pad = p->trace > 0 ? p->trace -1 : 0;
int ctr = p->pe->trace;
printf(PAD("PeInverter fwd:\n"));
if (p->trace <= 1)
printf(PAD(" Input %s\n"),icmPdv(p->inputChan, in));
if (!p->pe->attr.comp)
printf(PAD(" Pe %s bwd:\n"),icmPeSig2str(p->pe->etype));
p->pe->trace = p->trace + 1;
rv = p->pe->lookup_bwd(p->pe, out, in);
p->pe->trace = ctr;
if (!p->pe->attr.comp)
printf(PAD(" Output %s\n"),icmPdv(p->outputChan, out));
return rv;
}
static icmPe_lurv icmPeInverter_trace_lookup_bwd(icmPeInverter *p, double *out, double *in) {
icmPe_lurv rv = icmPe_lurv_OK;
int pad = p->trace > 0 ? p->trace -1 : 0;
int ctr = p->pe->trace;
printf(PAD("PeInverter bwd:\n"));
if (p->trace <= 1)
printf(PAD(" Input %s\n"),icmPdv(p->outputChan, in));
if (!p->pe->attr.comp)
printf(PAD(" Pe %s fwd:\n"),icmPeSig2str(p->pe->etype));
p->pe->trace = p->trace + 1;
rv = p->pe->lookup_fwd(p->pe, out, in);
p->pe->trace = ctr;
if (!p->pe->attr.comp)
printf(PAD(" Output %s\n"),icmPdv(p->inputChan, out));
return rv;
}
static icmPe_lurv icmPeInverter_lookup_fwd(icmPeInverter *p, double *out, double *in) {
if (p->trace)
return icmPeInverter_trace_lookup_fwd(p, out, in);
return p->pe->lookup_bwd(p->pe, out, in);
}
static icmPe_lurv icmPeInverter_lookup_bwd(icmPeInverter *p, double *out, double *in) {
if (p->trace)
return icmPeInverter_trace_lookup_bwd(p, out, in);
return p->pe->lookup_fwd(p->pe, out, in);
}
static int icmPeInverter_init(icmPeInverter *p) {
int rv;
if ((rv = p->pe->init(p->pe)) != ICM_ERR_OK)
return rv;
/* Simply reverse the attr's of what's being inverted */
/* and make our op be the Pe op */
ICM_PE_SETATTR(p->attr, 1, 0, p->pe->attr.norm, p->pe->attr.op, p->pe->attr.bwd,
p->pe->attr.fwd)
return ICM_ERR_OK;
}
/* Make Pe appear to be its inverse. */
/* icmPeInverter takes a reference. */
static icmPe *new_icmPeInverter(
icc *icp,
icmPe *pe
) {
ICM_PE_NS_ALLOCINIT(icmPeInverter, icmSigPeInverter)
p->init = icmPeInverter_init;
p->inputChan = pe->outputChan;
p->outputChan = pe->inputChan;
p->pe = pe->reference(pe);
p->lookup_fwd = icmPeInverter_lookup_fwd;
p->lookup_bwd = icmPeInverter_lookup_bwd;
return (icmPe *)p;
}
/* ------------------------------------------------------------------------------ */
/* XYZ abs <-> XYZ rel Pe (This is not a serialisable tagtype) */
static void icmPeAbs2Rel_dump(icmPeAbs2Rel *p, icmFile *op, int verb) {
int pad = p->dp;
if (p->attr.inv)
op->printf(op,PAD("PeRel2Abs:\n"));
else
op->printf(op,PAD("PeAbs2Rel:\n"));
}
static void icmPeAbs2Rel_del(icmPeAbs2Rel *p) {
if (p->refcount > 0 && --p->refcount == 0) {
p->icp->al->free(p->icp->al, p);
}
}
static icmPe_lurv icmPeAbs2Rel_lookup_fwd(icmPeAbs2Rel *p, double *out, double *in) {
icmMulBy3x3(out, p->lu->fromAbs, in);
return icmPe_lurv_OK;
}
static icmPe_lurv icmPeAbs2Rel_lookup_bwd(icmPeAbs2Rel *p, double *out, double *in) {
icmMulBy3x3(out, p->lu->toAbs, in);
return icmPe_lurv_OK;
}
static icmPe *new_icmPeAbs2Rel(
struct _icc *icp,
struct _icmLu4Space *lu, /* wp, toAbs, fromAbs */
int invert /* Rel->Abs */
) {
ICM_PE_NS_ALLOCINIT(icmPeAbs2Rel, icmSigPeAbs2Rel)
p->inputChan = p->outputChan = 3;
p->lu = lu;
ICM_PE_SETATTR(p->attr, 0, invert, 0, icmPeOp_matrix, 1, 1)
if (invert) {
p->lookup_fwd = icmPeAbs2Rel_lookup_bwd;
p->lookup_bwd = icmPeAbs2Rel_lookup_fwd;
} else {
p->lookup_fwd = icmPeAbs2Rel_lookup_fwd;
p->lookup_bwd = icmPeAbs2Rel_lookup_bwd;
}
return (icmPe *)p;
}
/* ------------------------------------------------------------------------------ */
/* XYZ <-> Lab Pe (This is not a serialisable tagtype) */
static void icmPeXYZ2Lab_dump(icmPeXYZ2Lab *p, icmFile *op, int verb) {
int pad = p->dp;
if (p->attr.inv)
op->printf(op,PAD("PeLab2XYZ:\n"));
else
op->printf(op,PAD("PeXYZ2Lab:\n"));
}
static void icmPeXYZ2Lab_del(icmPeXYZ2Lab *p) {
if (p->refcount > 0 && --p->refcount == 0) {
p->icp->al->free(p->icp->al, p);
}
}
static icmPe_lurv icmPeXYZ2Lab_lookup_fwd(icmPeXYZ2Lab *p, double *out, double *in) {
icmXYZ2Lab(&p->lu->pcswht, out, in);
return icmPe_lurv_OK;
}
static icmPe_lurv icmPeXYZ2Lab_lookup_bwd(icmPeXYZ2Lab *p, double *out, double *in) {
icmLab2XYZ(&p->lu->pcswht, out, in);
return icmPe_lurv_OK;
}
static icmPe *new_icmPeXYZ2Lab(
struct _icc *icp,
struct _icmLu4Space *lu, /* pcswht */
int invert /* Lab->XYZ */
) {
ICM_PE_NS_ALLOCINIT(icmPeXYZ2Lab, icmSigPeXYZ2Lab)
p->inputChan = p->outputChan = 3;
p->lu = lu;
ICM_PE_SETATTR(p->attr, 0, invert, 0, icmPeOp_matrix, 1, 1)
if (invert) {
p->lookup_fwd = icmPeXYZ2Lab_lookup_bwd;
p->lookup_bwd = icmPeXYZ2Lab_lookup_fwd;
} else {
p->lookup_fwd = icmPeXYZ2Lab_lookup_fwd;
p->lookup_bwd = icmPeXYZ2Lab_lookup_bwd;
}
return (icmPe *)p;
}
/* ------------------------------------------------------------------------------ */
/* XYZ <-> XYZ8 Pe (This is not a serialisable tagtype) */
static void icmPeXYZ2XYZ8_dump(icmPeXYZ2XYZ8 *p, icmFile *op, int verb) {
int pad = p->dp;
if (p->attr.inv)
op->printf(op,PAD("PeXYZ82XYZ:\n"));
else
op->printf(op,PAD("PeXYZ2XYZ8:\n"));
}
static void icmPeXYZ2XYZ8_del(icmPeXYZ2XYZ8 *p) {
if (p->refcount > 0 && --p->refcount == 0) {
p->icp->al->free(p->icp->al, p);
}
}
/* Convert XYZ to 8 bit Normalised */
static icmPe_lurv icmPeXYZ2XYZ8_lookup_fwd(icmPeXYZ2XYZ8 *p, double *out, double *in) {
out[0] = in[0] * (1.0/(1.0 + 127.0/128));
out[1] = in[1] * (1.0/(1.0 + 127.0/128));
out[2] = in[2] * (1.0/(1.0 + 127.0/128));
return icmPe_lurv_OK;
}
/* Convert 8 bit Normalised value XYZ */
static icmPe_lurv icmPeXYZ2XYZ8_lookup_bwd(icmPeXYZ2XYZ8 *p, double *out, double *in) {
out[0] = in[0] * (1.0 + 127.0/128); /* X */
out[1] = in[1] * (1.0 + 127.0/128); /* Y */
out[2] = in[2] * (1.0 + 127.0/128); /* Z */
return icmPe_lurv_OK;
}
static icmPe *new_icmPeXYZ2XYZ8(
struct _icc *icp,
int invert /* Lab->XYZ */
) {
ICM_PE_NS_ALLOCINIT(icmPeXYZ2XYZ8, icmSigPeXYZ2XYZ8)
p->inputChan = p->outputChan = 3;
if (invert) {
ICM_PE_SETATTR(p->attr, 0, invert, 1, icmPeOp_perch, 1, 1)
p->lookup_fwd = icmPeXYZ2XYZ8_lookup_bwd;
p->lookup_bwd = icmPeXYZ2XYZ8_lookup_fwd;
} else {
ICM_PE_SETATTR(p->attr, 0, invert, 1, icmPeOp_perch, 1, 1)
p->lookup_fwd = icmPeXYZ2XYZ8_lookup_fwd;
p->lookup_bwd = icmPeXYZ2XYZ8_lookup_bwd;
}
return (icmPe *)p;
}
/* ------------------------------------------------------------------------------ */
/* XYZ <-> XYZ16 Pe (This is not a serialisable tagtype) */
static void icmPeXYZ2XYZ16_dump(icmPeXYZ2XYZ16 *p, icmFile *op, int verb) {
int pad = p->dp;
if (p->attr.inv)
op->printf(op,PAD("PeXYZ162XYZ:\n"));
else
op->printf(op,PAD("PeXYZ2XYZ16:\n"));
}
static void icmPeXYZ2XYZ16_del(icmPeXYZ2XYZ16 *p) {
if (p->refcount > 0 && --p->refcount == 0) {
p->icp->al->free(p->icp->al, p);
}
}
/* Convert XYZ to 16 bit Normalised */
static icmPe_lurv icmPeXYZ2XYZ16_lookup_fwd(icmPeXYZ2XYZ16 *p, double *out, double *in) {
out[0] = in[0] * (1.0/(1.0 + 32767.0/32768));
out[1] = in[1] * (1.0/(1.0 + 32767.0/32768));
out[2] = in[2] * (1.0/(1.0 + 32767.0/32768));
return icmPe_lurv_OK;
}
/* Convert 16 bit Normalised value XYZ */
static icmPe_lurv icmPeXYZ2XYZ16_lookup_bwd(icmPeXYZ2XYZ16 *p, double *out, double *in) {
out[0] = in[0] * (1.0 + 32767.0/32768); /* X */
out[1] = in[1] * (1.0 + 32767.0/32768); /* Y */
out[2] = in[2] * (1.0 + 32767.0/32768); /* Z */
return icmPe_lurv_OK;
}
static icmPe *new_icmPeXYZ2XYZ16(
struct _icc *icp,
int invert /* Lab->XYZ */
) {
ICM_PE_NS_ALLOCINIT(icmPeXYZ2XYZ16, icmSigPeXYZ2XYZ16)
p->inputChan = p->outputChan = 3;
if (invert) {
ICM_PE_SETATTR(p->attr, 0, invert, 1, icmPeOp_perch, 1, 1)
p->lookup_fwd = icmPeXYZ2XYZ16_lookup_bwd;
p->lookup_bwd = icmPeXYZ2XYZ16_lookup_fwd;
} else {
ICM_PE_SETATTR(p->attr, 0, invert, 1, icmPeOp_perch, 1, 1)
p->lookup_fwd = icmPeXYZ2XYZ16_lookup_fwd;
p->lookup_bwd = icmPeXYZ2XYZ16_lookup_bwd;
}
return (icmPe *)p;
}
/* ------------------------------------------------------------------------------ */
/* Lab <-> Lab8 Pe (This is not a serialisable tagtype) */
static void icmPeLab2Lab8_dump(icmPeLab2Lab8 *p, icmFile *op, int verb) {
int pad = p->dp;
if (p->attr.inv)
op->printf(op,PAD("PeLab82Lab:\n"));
else
op->printf(op,PAD("PeLab2Lab8:\n"));
}
static void icmPeLab2Lab8_del(icmPeLab2Lab8 *p) {
if (p->refcount > 0 && --p->refcount == 0) {
p->icp->al->free(p->icp->al, p);
}
}
/* Convert Lab to 8 bit Normalised */
static icmPe_lurv icmPeLab2Lab8_lookup_fwd(icmPeLab2Lab8 *p, double *out, double *in) {
out[0] = in[0] * 1.0/100.0; /* L */
out[1] = (in[1] + 128.0) * 1.0/255.0; /* a */
out[2] = (in[2] + 128.0) * 1.0/255.0; /* b */
return icmPe_lurv_OK;
}
/* Convert 8 bit Normalised value to Lab */
static icmPe_lurv icmPeLab2Lab8_lookup_bwd(icmPeLab2Lab8 *p, double *out, double *in) {
out[0] = in[0] * 100.0; /* L */
out[1] = (in[1] * 255.0) - 128.0; /* a */
out[2] = (in[2] * 255.0) - 128.0; /* b */
return icmPe_lurv_OK;
}
static icmPe *new_icmPeLab2Lab8(
struct _icc *icp,
int invert /* Lab->Lab */
) {
ICM_PE_NS_ALLOCINIT(icmPeLab2Lab8, icmSigPeLab2Lab8)
p->inputChan = p->outputChan = 3;
if (invert) {
ICM_PE_SETATTR(p->attr, 0, invert, 1, icmPeOp_perch, 1, 1)
p->lookup_fwd = icmPeLab2Lab8_lookup_bwd;
p->lookup_bwd = icmPeLab2Lab8_lookup_fwd;
} else {
ICM_PE_SETATTR(p->attr, 0, invert, 1, icmPeOp_perch, 1, 1)
p->lookup_fwd = icmPeLab2Lab8_lookup_fwd;
p->lookup_bwd = icmPeLab2Lab8_lookup_bwd;
}
return (icmPe *)p;
}
/* ------------------------------------------------------------------------------ */
/* Lab <-> LabV2 Pe (This is not a serialisable tagtype) */
static void icmPeLab2LabV2_dump(icmPeLab2LabV2 *p, icmFile *op, int verb) {
int pad = p->dp;
if (p->attr.inv)
op->printf(op,PAD("PeLabV22Lab:\n"));
else
op->printf(op,PAD("PeLab2LabV2:\n"));
}
static void icmPeLab2LabV2_del(icmPeLab2LabV2 *p) {
if (p->refcount > 0 && --p->refcount == 0) {
p->icp->al->free(p->icp->al, p);
}
}
/* Convert Lab to 16 bit V2 Normalised */
static icmPe_lurv icmPeLab2LabV2_lookup_fwd(icmPeLab2LabV2 *p, double *out, double *in) {
out[0] = in[0] * 65280.0/(100.0 * 65535.0); /* L */
out[1] = (in[1] + 128.0) * 65280.0/(255.0 * 65535.0); /* a */
out[2] = (in[2] + 128.0) * 65280.0/(255.0 * 65535.0); /* b */
return icmPe_lurv_OK;
}
/* Convert 16 bit V2 Normalised value to Lab */
static icmPe_lurv icmPeLab2LabV2_lookup_bwd(icmPeLab2LabV2 *p, double *out, double *in) {
out[0] = in[0] * (100.0 * 65535.0)/65280.0; /* L */
out[1] = (in[1] * (255.0 * 65535.0)/65280) - 128.0; /* a */
out[2] = (in[2] * (255.0 * 65535.0)/65280) - 128.0; /* b */
return icmPe_lurv_OK;
}
static icmPe *new_icmPeLab2LabV2(
struct _icc *icp,
int invert /* Lab->Lab */
) {
ICM_PE_NS_ALLOCINIT(icmPeLab2LabV2, icmSigPeLab2LabV2)
p->inputChan = p->outputChan = 3;
if (invert) {
ICM_PE_SETATTR(p->attr, 0, invert, 1, icmPeOp_perch, 1, 1)
p->lookup_fwd = icmPeLab2LabV2_lookup_bwd;
p->lookup_bwd = icmPeLab2LabV2_lookup_fwd;
} else {
ICM_PE_SETATTR(p->attr, 0, invert, 1, icmPeOp_perch, 1, 1)
p->lookup_fwd = icmPeLab2LabV2_lookup_fwd;
p->lookup_bwd = icmPeLab2LabV2_lookup_bwd;
}
return (icmPe *)p;
}
/* ------------------------------------------------------------------------------ */
/* Generic <-> Normalized Pe (This is not a serialisable tagtype) */
/* min/max difference is sanity limited to be at least 1e-4. */
/* (The normalized range is 0.0 - 1.0) */
static void icmPeGeneric2Norm_dump(icmPeGeneric2Norm *p, icmFile *op, int verb) {
int pad = p->dp;
if (p->attr.inv) {
op->printf(op,PAD("Norm2Generic (%s):\n"),p->ident);
} else {
op->printf(op,PAD("PeGeneric2Norm (%s):\n"), p->ident);
}
if (verb >= 1) {
op->printf(op,PAD(" full min %s, max %s\n"),icmPdv(p->inputChan, p->min),icmPdv(p->inputChan, p->max));
op->printf(op,PAD(" norm min %s, max %s\n"),icmPdv(p->inputChan, p->nmin),icmPdv(p->inputChan, p->nmax));
}
}
static void icmPeGeneric2Norm_del(icmPeGeneric2Norm *p) {
if (p->refcount > 0 && --p->refcount == 0) {
p->icp->al->free(p->icp->al, p);
}
}
/* Convert full range to normalised */
static icmPe_lurv icmPeGeneric2Norm_lookup_fwd(icmPeGeneric2Norm *p, double *out, double *in) {
unsigned int i;
for (i = 0; i < p->inputChan; i++) {
double val = (in[i] - p->min[i])/(p->max[i] - p->min[i]);
out[i] = val * (p->nmax[i] - p->nmin[i]) + p->nmin[i];
}
return icmPe_lurv_OK;
}
/* Convert from normalised to full range */
static icmPe_lurv icmPeGeneric2Norm_lookup_bwd(icmPeGeneric2Norm *p, double *out, double *in) {
unsigned int i;
for (i = 0; i < p->inputChan; i++) {
double val = (in[i] - p->nmin[i])/(p->nmax[i] - p->nmin[i]);
out[i] = val * (p->max[i] - p->min[i]) + p->min[i];
}
return icmPe_lurv_OK;
}
static icmPe *new_icmPeFullyGeneric2Norm(
struct _icc *icp,
unsigned int nchan,
double *min, double *max, /* Per channel range to be normalised */
double *nmin, double *nmax, /* Per channel range to normalize to (NULL = 0..1) */
char *ident, /* Encoding identifier */
int invert
) {
unsigned int i;
ICM_PE_NS_ALLOCINIT(icmPeGeneric2Norm, icmSigPeGeneric2Norm)
p->inputChan = p->outputChan = nchan;
for (i = 0; i < nchan; i++) {
if (min[i] > max[i]) { /* Hmm */
double tt = min[i];
min[i] = max[i];
max[i] = tt;
}
p->min[i] = min[i];
p->max[i] = max[i];
if ((p->max[i] - p->min[i]) < 1e-4) {
p->max[i] += 0.5e-4;
p->min[i] -= 0.5e-4;
}
if (nmin == NULL)
p->nmin[i] = 0.0;
else
p->nmin[i] = nmin[i];
if (nmax == NULL)
p->nmax[i] = 1.0;
else
p->nmax[i] = nmax[i];
if (p->nmin[i] > p->nmax[i]) { /* Hmm */
double tt = p->nmin[i];
p->nmin[i] = p->nmax[i];
p->nmax[i] = tt;
}
if ((p->nmax[i] - p->nmin[i]) < 1e-4) {
p->nmax[i] += 0.5e-4;
p->nmin[i] -= 0.5e-4;
}
}
strncpy(p->ident, ident, 50); p->ident[49] = '\000';
if (invert) {
ICM_PE_SETATTR(p->attr, 0, invert, 1, icmPeOp_perch, 1, 1)
p->lookup_fwd = icmPeGeneric2Norm_lookup_bwd;
p->lookup_bwd = icmPeGeneric2Norm_lookup_fwd;
} else {
ICM_PE_SETATTR(p->attr, 0, invert, 1, icmPeOp_perch, 1, 1)
p->lookup_fwd = icmPeGeneric2Norm_lookup_fwd;
p->lookup_bwd = icmPeGeneric2Norm_lookup_bwd;
}
return (icmPe *)p;
}
static icmPe *new_icmPeGeneric2Norm(
struct _icc *icp,
unsigned int nchan,
double *min, double *max, /* Per channel range to be normalised */
char *ident, /* Encoding identifier */
int invert
) {
return new_icmPeFullyGeneric2Norm(icp, nchan, min, max, NULL, NULL, ident, invert);
}
/* ------------------------------------------------------------------------------ */
/* A point to grid alignment transform. */
/* This creates a per channel transform that piecwize bends */
/* the input so as to land the given point on a grid point. */
/* The values are assumed in the range 0..1 */
static void icmPeGridAlign_dump(icmPeGridAlign *p, icmFile *op, int verb) {
int pad = p->dp;
op->printf(op,PAD("PeGridAlign:\n"));
op->printf(op,PAD(" src %s\n"),icmPdv(p->inputChan, p->src));
op->printf(op,PAD(" dst %s\n"),icmPdv(p->inputChan, p->dst));
}
static void icmPeGridAlign_del(icmPeGridAlign *p) {
if (p->refcount > 0 && --p->refcount == 0) {
p->icp->al->free(p->icp->al, p);
}
}
/* Align the src point to the dst */
static icmPe_lurv icmPeGridAlign_lookup_fwd(icmPeGridAlign *p, double *out, double *in) {
unsigned int i;
for (i = 0; i < p->inputChan; i++) {
if (in[i] <= p->src[i])
out[i] = in[i] * p->fwdlow[i];
else
out[i] = 1.0 - (1.0 - in[i]) * p->fwdhigh[i];
}
return icmPe_lurv_OK;
}
/* Align the dst point to the src */
static icmPe_lurv icmPeGridAlign_lookup_bwd(icmPeGridAlign *p, double *out, double *in) {
unsigned int i;
for (i = 0; i < p->inputChan; i++) {
if (in[i] < p->dst[i])
out[i] = p->bwdlowoff[i] + in[i] * p->bwdlow[i];
else
out[i] = p->bwdhighoff[i] + 1.0 - (1.0 - in[i]) * p->bwdhigh[i];
}
return icmPe_lurv_OK;
}
static icmPe *new_icmPeGridAlign(
struct _icc *icp,
unsigned int nchan,
double *point, /* Point to aim for, must be 0..1 */
unsigned int *gridres, /* Grid resolution for each channel */
int invert
) {
unsigned int i;
ICM_PE_NS_ALLOCINIT(icmPeGridAlign, icmSigPeGridAlign)
p->inputChan = p->outputChan = nchan;
icmClipN(p->src, point, nchan); /* Just to be sure... */
for (i = 0; i < nchan; i++) {
unsigned int ix;
/* Skip any grid with a low res */
if (gridres[i] < 9) {
/* Make this a NOP */
p->bwdlowoff[i] = p->bwdhighoff[i] = 0.0;
p->src[i] = p->dst[i] = 0.5;
p->fwdlow[i] = 1.0;
p->fwdhigh[i] = 1.0;
p->bwdlow[i] = 1.0;
p->bwdhigh[i] = 1.0;
continue;
}
/* Closest index to src point. This sets our raw destination. */
ix = (unsigned int)floor(p->src[i] * (gridres[i]-1.0) + 0.5);
p->dst[i] = ix/(gridres[i]-1.0);
if (fabs(p->src[i]) > 1e-9)
p->fwdlow[i] = p->dst[i]/p->src[i];
else
p->fwdlow[i] = 1.0; /* dst must be zero too... */
if (fabs(p->src[i] - 1.0) > 1e-9)
p->fwdhigh[i] = (1.0 - p->dst[i])/(1.0 - p->src[i]);
else
p->fwdhigh[i] = 1.0; /* dst must be zero too... */
p->bwdlowoff[i] = p->bwdhighoff[i] = 0.0;
/* For completeness we handle the case where src != edge and dst == edge: */
if (fabs(p->dst[i]) > 1e-9)
p->bwdlow[i] = p->src[i]/p->dst[i];
else {
p->bwdlow[i] = 0.0; /* clip to src */
p->bwdlowoff[i] = p->src[i];
}
if (fabs(p->dst[i] - 1.0) > 1e-9)
p->bwdhigh[i] = (1.0 - p->src[i])/(1.0 - p->dst[i]);
else {
p->bwdhigh[i] = 0.0; /* clip to src */
p->bwdhighoff[i] = p->src[i] - 1.0;
}
}
if (invert) {
ICM_PE_SETATTR(p->attr, 0, invert, 0, icmPeOp_perch, 1, 1)
p->lookup_fwd = icmPeGridAlign_lookup_bwd;
p->lookup_bwd = icmPeGridAlign_lookup_fwd;
} else {
ICM_PE_SETATTR(p->attr, 0, invert, 0, icmPeOp_perch, 1, 1)
p->lookup_fwd = icmPeGridAlign_lookup_fwd;
p->lookup_bwd = icmPeGridAlign_lookup_bwd;
}
#ifdef NEVER
{
double tin[MAX_CHAN], tout[MAX_CHAN];
printf("~1 src %s\n",icmPdv(p->inputChan, p->src));
printf("~1 dst %s\n",icmPdv(p->inputChan, p->dst));
printf("~1 fwdlow %s\n",icmPdv(p->inputChan, p->fwdlow));
printf("~1 fwdhigh %s\n",icmPdv(p->inputChan, p->fwdhigh));
printf("~1 bwdlow %s\n",icmPdv(p->inputChan, p->bwdlow));
printf("~1 bwdlowoff %s\n",icmPdv(p->inputChan, p->bwdlowoff));
printf("~1 bwdhigh %s\n",icmPdv(p->inputChan, p->bwdhigh));
printf("~1 bwdhighoff %s\n",icmPdv(p->inputChan, p->bwdhighoff));
icmCpyN(tin, point, p->inputChan);
p->lookup_fwd(p, tout, tin);
printf("~1 in %s\n",icmPdv(p->inputChan, tin));
printf("~1 out %s\n",icmPdv(p->inputChan, tout));
p->lookup_bwd(p, tin, tout);
printf("~1 in %s\n\n",icmPdv(p->inputChan, tin));
icmSetN(tin, 0.0, p->inputChan);
p->lookup_fwd(p, tout, tin);
printf("~1 in %s\n",icmPdv(p->inputChan, tin));
printf("~1 out %s\n",icmPdv(p->inputChan, tout));
p->lookup_bwd(p, tin, tout);
printf("~1 in %s\n\n",icmPdv(p->inputChan, tin));
icmSetN(tin, 1.0, p->inputChan);
p->lookup_fwd(p, tout, tin);
printf("~1 in %s\n",icmPdv(p->inputChan, tin));
printf("~1 out %s\n",icmPdv(p->inputChan, tout));
p->lookup_bwd(p, tin, tout);
printf("~1 in %s\n\n",icmPdv(p->inputChan, tin));
}
#endif /* NEVER */
return (icmPe *)p;
}
/* ------------------------------------------------------------------------------ */
/* A NOP (This is not a serialisable tagtype) */
static void icmPeNOP_dump(icmPeNOP *p, icmFile *op, int verb) {
int pad = p->dp;
op->printf(op,PAD("PeNOP:\n"));
}
static void icmPeNOP_del(icmPeNOP *p) {
if (p->refcount > 0 && --p->refcount == 0) {
p->icp->al->free(p->icp->al, p);
}
}
static icmPe_lurv icmPeNOP_lookup(icmPeNOP *p, double *out, double *in) {
if (out != in) {
unsigned int i;
for (i = 0; i < p->inputChan; i++)
out[i] = in[i];
}
return icmPe_lurv_OK;
}
static icmPe *new_icmPeNOP(
struct _icc *icp,
unsigned int nchan
) {
ICM_PE_NS_ALLOCINIT(icmPeNOP, icmSigPeNOP)
p->inputChan = p->outputChan = nchan;
ICM_PE_SETATTR(p->attr, 0, 0, 0, icmPeOp_perch, 1, 1)
p->lookup_fwd = icmPeNOP_lookup;
p->lookup_bwd = icmPeNOP_lookup;
return (icmPe *)p;
}
/* ====================================================================== */
/* Processing Element Container (Not serialisable) */
/* Dump a human readable description */
static void icmPeContainer_dump(icmPeContainer *p, icmFile *op, int verb) {
int pad = p->dp;
unsigned int i;
op->printf(op,PAD("PeContainer:\n"));
op->printf(op,PAD(" Attributes = %s\n"),icmPe_Attr2Str(&p->attr));
op->printf(op,PAD(" Input Channels = %u\n"),p->inputChan);
op->printf(op,PAD(" Output Channels = %u\n"),p->outputChan);
op->printf(op,PAD(" No. elements = %u\n"),p->count);
for (i = 0; i < p->count; i++) {
op->printf(op,PAD(" Element %u:\n"),i);
if (p->pe[i]->etype == icmSigPeContainer) {
p->pe[i]->dp = pad + 2;
p->pe[i]->dump(p->pe[i], op, verb);
} else {
op->printf(op,PAD(" PeType = %s\n"),icmPeSig2str(p->pe[i]->etype));
}
}
}
/* Add a Pe to the end of the sequence. */
/* Any empty or attr == NOP are skipped. */
static int icmPeContainer_append(icmPeContainer *p, icmPe *pe) {
if (pe == NULL || pe->attr.op == icmPeOp_NOP)
return ICM_ERR_OK;
p->count++;
if (icmArrayResize(p->icp, &p->_count, &p->count,
(void **)&p->pe, sizeof(icmPe *), "icmPeContainer array")) {
return ICM_ERR_MALLOC;
}
pe->reference(pe);
p->pe[p->count-1] = pe;
return ICM_ERR_OK;
}
/* Add a Pe to the end of the sequence */
static int icmPeContainer_prepend(icmPeContainer *p, icmPe *pe) {
return p->insert(p, 0, pe);
}
/* Insert a Pe before the given index */
static int icmPeContainer_insert(icmPeContainer *p, unsigned int ix, icmPe *pe) {
unsigned int i;
if (ix >= p->count)
return icm_err(p->icp, ICM_ERR_PECONT_BOUND, "icmPeContainer_insert ix bounds");
/* We know count is at least 1 now.. */
p->count++;
if (icmArrayResize(p->icp, &p->_count, &p->count,
(void **)&p->pe, sizeof(icmPe *), "icmPeContainer array")) {
return ICM_ERR_MALLOC;
}
for (i = p->count-1; i > ix; i--)
p->pe[i] = p->pe[i-1];
pe->reference(pe);
p->pe[ix] = pe;
return ICM_ERR_OK;
}
/* Replace the Pe at the given index */
static int icmPeContainer_replace(icmPeContainer *p, unsigned int ix, icmPe *pe) {
if (ix >= p->count)
return icm_err(p->icp, ICM_ERR_PECONT_BOUND, "icmPeContainer_replace ix bounds");
p->pe[ix]->del(p->pe[ix]);
pe->reference(pe);
p->pe[ix] = pe;
return ICM_ERR_OK;
}
/* Remove the Pe at the given index. */
static int icmPeContainer_remove(icmPeContainer *p, unsigned int ix) {
unsigned int i;
if (ix >= p->count)
return icm_err(p->icp, ICM_ERR_PECONT_BOUND, "icmPeContainer_remove ix bounds");
/* We know count is at least 1 now.. */
p->pe[ix]->del(p->pe[ix]);
for (i = ix; i < (p->count-1); i++)
p->pe[i] = p->pe[i+1];
p->count--;
if (icmArrayResize(p->icp, &p->_count, &p->count,
(void **)&p->pe, sizeof(icmPe *), "icmPeContainer array")) {
return ICM_ERR_MALLOC;
}
return ICM_ERR_OK;
}
/* Append Pe's from another icmPeContainer, */
/* starting at six and ending before eix. */
/* Any empty or attr == NOP are skipped. */
/* This will flatten any sub-icmPeSeq, resulting in a sequence */
/* of direct Pe's or direct Pe's inside an Inverter. */
static int icmPeContainer_append_pes(icmPeContainer *p, icmPeSeq *src, int six, int eix) {
int ix;
for (ix = six; ix < src->count && ix < eix; ix++) {
if (src->pe[ix] != NULL && src->pe[ix]->attr.op != icmPeOp_NOP) {
int rv;
/* We need to recurse */
if (src->pe[ix]->isPeSeq) {
icmPeSeq *rsrc = (icmPeSeq *)src->pe[ix];
if ((rv = p->append_pes(p, rsrc, 0, rsrc->count)) != ICM_ERR_OK)
return rv;
} else {
if (src->pe[ix]->etype == icmSigPeInverter
&& ((icmPeInverter *)src->pe[ix])->pe->isPeSeq) {
/* We're not currently handling an icmPeSeq inside an inverter. */
/* We could add support if needed by adding each element in */
/* reverse order each inside its own inverter. Inverting an */
/* inverter would remove the need for an Inverter, etc. */
return icm_err(p->icp, ICM_ERR_APPEND_PES_INTERNAL,"icmPeContainer_append_pes found icmPeSeq within inverter - we don't handle that at the moment!");
} else {
if ((rv = p->append(p, src->pe[ix])) != 0)
return rv;
}
}
}
}
return ICM_ERR_OK;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Various icmPeContainer/icmPeSeq characteristic analysis methods: */
/* These all assume that the icmPeContainer is in a flattened state. */
/* Find the largest per channel input lut resolution. */
/* res may be NULL */
/* Returns 0 if there is no per channel lut in the input sequence. */
static unsigned int icmPeContainer_max_in_res(icmPeContainer *p, int res[MAX_CHAN]) {
unsigned int ix, e;
unsigned int maxres = 0;
if (res != NULL) {
for (e = 0; e < MAX_CHAN; e++)
res[e] = 0;
}
/* Search forwards */
for (ix = 0; ix < p->count; ix++) {
icmPe *pe = p->pe[ix];
if (pe == NULL)
continue;
if (pe->isPeSeq) {
icm_err(p->icp, ICM_ERR_SEARCH_PESEQ_INTERNAL, "icmPeContainer_max_in_res found unexpected icmPeSeq inside icmPeContainer");
return 0;
} else if (pe->etype == icmSigPeInverter) {
pe = ((icmPeInverter *)pe)->pe;
}
if ((pe->etype == icmSigPeMatrix
|| pe->etype == icmSigPeClut
|| pe->etype == icmSigPeMono)
&& pe->attr.op != icmPeOp_NOP) {
break; /* Assume end of input sequence */
}
if (pe->etype == icmSigPeCurve) {
icmPeCurve *cv = (icmPeCurve *)pe;
if (cv->count > maxres)
maxres = cv->count;
if (res != NULL && cv->count > res[0])
res[0] = cv->count;
} else if (pe->etype == icmSigPeCurveSet) {
icmPeCurveSet *cs = (icmPeCurveSet *)pe;
for (e = 0; e < cs->inputChan; e++) {
if (cs->pe[e]->etype == icmSigPeCurve) {
icmPeCurve *cv = (icmPeCurve *)cs->pe[e];
if (cv->count > maxres)
maxres = cv->count;
if (res != NULL && cv->count > res[e])
res[e] = cv->count;
}
}
}
}
return maxres;
}
/* Find the largest cLUT clutPoints[] value. */
/* Returns 0 if there is no cLut transform in the sequence. */
static unsigned int icmPeContainer_max_clut_res(icmPeContainer *p, int res[MAX_CHAN]) {
unsigned int ix, e;
unsigned int maxres = 0;
if (res != NULL) {
for (e = 0; e < MAX_CHAN; e++)
res[e] = 0;
}
for (ix = 0; ix < p->count; ix++) {
icmPe *pe = p->pe[ix];
if (pe == NULL)
continue;
if (pe->isPeSeq) {
icm_err(p->icp, ICM_ERR_SEARCH_PESEQ_INTERNAL, "icmPeContainer_max_clut_res found unexpected icmPeSeq inside icmPeContainer");
return 0;
/* Is this likely ? Does it make any sense if it existed ?? */
} else if (pe->etype == icmSigPeInverter) {
pe = ((icmPeInverter *)pe)->pe;
}
if (pe->etype == icmSigPeClut) {
icmPeClut *cl = (icmPeClut *)pe;
for (e = 0; e < cl->inputChan; e++) {
if (cl->clutPoints[e] > maxres)
maxres = cl->clutPoints[e];
if (res != NULL && cl->clutPoints[e] > res[e]) {
res[e] = cl->clutPoints[e];
}
}
}
}
return maxres;
}
/* Find the largest per channel output lut resolution. */
/* res may be NULL */
/* Returns 0 if there is no per channel lut out the output sequence. */
static unsigned int icmPeContainer_max_out_res(icmPeContainer *p, int res[MAX_CHAN]) {
unsigned int ix, e;
unsigned int maxres = 0;
if (res != NULL) {
for (e = 0; e < MAX_CHAN; e++)
res[e] = 0;
}
/* Search backwards */
for (ix = p->count; ix-- > 0;) {
icmPe *pe = p->pe[ix];
if (pe == NULL)
continue;
if (pe->isPeSeq) {
icm_err(p->icp, ICM_ERR_SEARCH_PESEQ_INTERNAL, "icmPeContainer_max_in_res found unexpected icmPeSeq inside icmPeContainer");
return 0;
} else if (pe->etype == icmSigPeInverter) {
pe = ((icmPeInverter *)pe)->pe;
}
if ((pe->etype == icmSigPeMatrix
|| pe->etype == icmSigPeClut
|| pe->etype == icmSigPeMono)
&& pe->attr.op != icmPeOp_NOP) {
break; /* Assume end of input sequence */
}
if (pe->etype == icmSigPeCurve) {
icmPeCurve *cv = (icmPeCurve *)pe;
if (cv->count > maxres)
maxres = cv->count;
if (res != NULL && cv->count > res[0])
res[0] = cv->count;
} else if (pe->etype == icmSigPeCurveSet) {
icmPeCurveSet *cs = (icmPeCurveSet *)pe;
for (e = 0; e < cs->inputChan; e++) {
if (cs->pe[e]->etype == icmSigPeCurve) {
icmPeCurve *cv = (icmPeCurve *)cs->pe[e];
if (cv->count > maxres)
maxres = cv->count;
if (res != NULL && cv->count > res[e])
res[e] = cv->count;
}
}
}
}
return maxres;
}
/* Determine if the per channel input or output is likely to be */
/* in a linear light space. We do this by seeing if the first */
/* non-per channel or format element is a matrix or matrix like */
/* element. */
/* dir should be 0 if for if we are interested in the input characteristic, */
/* and nz if we are interested in the output characteristic. */
/* (We assume that the icmPeContainer is flattened) */
static int icmPeContainer_linear_light_inout(icmPeContainer *p, int dir) {
int ix, ix_s, ix_i, ix_e;
unsigned int e;
p->init(p); /* Don't wonder if it is inited */
if (dir) {
ix_s = p->count-1;
ix_i = -1;
ix_e = -1;
} else {
ix_s = 0;
ix_i = 1;
ix_e = p->count;
}
for (ix = ix_s; ix != ix_e; ix += ix_i) {
icmPe *pe = p->pe[ix];
if (pe == NULL)
continue;
if (pe->isPeSeq) {
icm_err(p->icp, ICM_ERR_SEARCH_PESEQ_INTERNAL, "icmPeContainer_linear_light_inout found unexpected icmPeSeq inside icmPeContainer");
return 0;
} else if (pe->etype == icmSigPeInverter) {
pe = ((icmPeInverter *)pe)->pe;
}
if (pe->attr.op == icmPeOp_complex) { /* Hmm. Shouldn't happen if flattened ? */
icm_err(p->icp, ICM_ERR_SEARCH_PESEQ_INTERNAL, "icmPeContainer_linear_light_inout found unexpected icmPeSeq op = icmPeOp_complex");
return 0;
}
/* Ignore these ones... */
if (pe->attr.op == icmPeOp_NOP
|| pe->attr.op == icmPeOp_perch
|| pe->attr.op == icmPeOp_fmt) {
continue;
}
if (pe->etype == icmSigPeMatrix) {
return 1;
/* We're assuming that a cLut with a res. of 2 is really a type of matrix... */
} else if (pe->etype == icmSigPeClut) {
icmPeClut *cl = (icmPeClut *)p->pe[ix];
for (e = 0; e < cl->inputChan; e++) {
if (cl->clutPoints[e] > 2)
return 0;
}
return 1;
} else {
icm_err(p->icp, ICM_ERR_SEARCH_PESEQ_INTERNAL, "icmPeContainer_linear_light_inout found unexpected icmPeSeq op = %s, etype = %s",icmPe_Op2str(pe->attr.op), icmPeSig2str(pe->etype));
return 0;
}
}
return 0;
}
/* Return a pointer to the last icmPeLut */
/* and a Seq containing the transforms from the output of the Lut. */
/* Return NULL if no icmPeLut */
/* Delete both objects when done */
static icmPeClut *icmPeContainer_get_lut(
icmPeContainer *p, /* full transform sequence */
icmPeContainer **ptail /* If not NULL return tail process, NULL if none */
) {
int ix;
/* Search backwards */
for (ix = p->count; ix-- > 0;) {
icmPe *pe = p->pe[ix];
if (pe == NULL)
continue;
if (pe->isPeSeq) {
icm_err(p->icp, ICM_ERR_SEARCH_PESEQ_INTERNAL, "icmPeContainer_get_lut found unexpected icmPeSeq inside icmPeContainer");
return NULL;
}
/* Ignore any inverter - cLut will never be inside an inverter... */
if (pe->etype == icmSigPeClut) {
icmPeClut *cl = (icmPeClut *)pe;
icmPeContainer *tail = NULL;
double sum;
tail = new_icmPeContainer(p->icp, 0, 0);
tail->append_pes(tail, (icmPeSeq *)p, ix+1, p->count);
tail->init(tail);
if (tail->attr.op == icmPeOp_NOP) {
tail->del(tail);
tail = NULL;
}
if (ptail != NULL)
*ptail = tail;
return cl->reference(cl);
}
}
return NULL;
}
/* Return total ink limit and channel maximums. */
/* return -1.0 if this lu doesn't have an icmPeClut in it. */
static double icmPeContainer_get_tac(
icmPeContainer *p,
double *chmax, /* device return channel maximums. May be NULL */
void (*calfunc)(void *cntx, double *out, double *in), /* Optional calibration func. */
void *cntx
) {
icmPeClut *cl;
icmPeContainer *tail; /* If not NULL return tail process, NULL if none */
double sum;
if ((cl = icmPeContainer_get_lut(p, &tail)) == NULL) {
return 1;
}
sum = cl->get_tac(cl, chmax, (icmPe *)tail, calfunc, cntx);
cl->del(cl);
if (tail != NULL)
tail->del(tail);
return sum;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Delete the container and all its contents */
static void icmPeContainer_del(icmPeContainer *p) {
if (p->refcount > 0 && --p->refcount == 0) {
unsigned int i;
for (i = 0; i < p->count; i++)
p->pe[i]->del(p->pe[i]); /* Deletes if last reference */
ICMFREEARRAY(p->icp, p->_count, p->pe)
p->icp->al->free(p->icp->al, p);
}
}
/* Create an empty object. Return null on error */
static icmPeContainer *new_icmPeContainer(
icc *icp,
int dinch, /* Default number of input channels, 0 OK if will add non-NULL memb. Pe. */
int doutch /* Default number of output channels, 0 OK if will add non-NULL memb. Pe. */
) {
ICM_PE_SEQ_NS_ALLOCINIT(icmPeContainer, icmSigPeContainer)
p->inputChan = dinch;
p->outputChan = doutch;
ICM_PE_SETATTR(p->attr, 1, 0, 0, icmPeOp_NOP, 1, 1)
p->append = icmPeContainer_append;
p->prepend = icmPeContainer_prepend;
p->insert = icmPeContainer_insert;
p->replace = icmPeContainer_replace;
p->remove = icmPeContainer_remove;
p->append_pes = icmPeContainer_append_pes;
p->max_in_res = icmPeContainer_max_in_res;
p->max_clut_res = icmPeContainer_max_clut_res;
p->max_out_res = icmPeContainer_max_out_res;
p->linear_light_inout = icmPeContainer_linear_light_inout;
p->get_lut = icmPeContainer_get_lut;
p->get_tac = icmPeContainer_get_tac;
return p;
}