2757 lines
76 KiB
C
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;
|
|
}
|
|
|