1556 lines
50 KiB
C
1556 lines
50 KiB
C
|
|
/*
|
|
* Argyll Color Management System
|
|
* Fake print target chart reader - use ICC or MPP profile rather than instrument
|
|
*
|
|
* Author: Graeme W. Gill
|
|
* Date: 17/2/2002
|
|
*
|
|
* Copyright 2002 - 2013 Graeme W. Gill
|
|
* All rights reserved.
|
|
*
|
|
* This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
|
|
* see the License.txt file for licencing details.
|
|
*/
|
|
|
|
/*
|
|
* TTBD:
|
|
|
|
This utility should really be merged with cctiff ?
|
|
ie. Add cgats i/o to cctiff
|
|
Add other processing steps such as TV, BT.1886, random to cctiff.
|
|
|
|
If a display profile has the icSigLuminanceTag, the value
|
|
should be used to set the .ti3 LUMINANCE_XYZ_CDM2 value,
|
|
and set NORMALIZED_TO_Y_100 to YES.
|
|
|
|
*/
|
|
|
|
|
|
#undef DEBUG
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
#include <sys/types.h>
|
|
#include <time.h>
|
|
#include <string.h>
|
|
#include "copyright.h"
|
|
#include "aconfig.h"
|
|
#include "numlib.h"
|
|
#include "cgats.h"
|
|
#include "conv.h"
|
|
#include "xicc.h"
|
|
#include "bt1886.h"
|
|
#include "icc.h"
|
|
#include "ui.h"
|
|
|
|
void usage(char *diag, ...) {
|
|
fprintf(stderr,"Fake test chart reader - lookup values in ICC/MPP profile, Version %s\n",
|
|
ARGYLL_VERSION_STR);
|
|
fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
|
|
if (diag != NULL) {
|
|
va_list args;
|
|
fprintf(stderr," Diagnostic: ");
|
|
va_start(args, diag);
|
|
vfprintf(stderr, diag, args);
|
|
va_end(args);
|
|
fprintf(stderr,"\n");
|
|
}
|
|
/* Keep options in order of processing */
|
|
fprintf(stderr,"usage: fakeread [-options] profile.[%s|mpp|ti3] outfile\n",ICC_FILE_EXT_ND);
|
|
fprintf(stderr," -v [n] Verbose mode [level]\n");
|
|
fprintf(stderr," -e flag Video encode device input to sepration as:\n");
|
|
fprintf(stderr," n normal 0..1 full range RGB levels (default)\n");
|
|
fprintf(stderr," t (16-235)/255 \"TV\" RGB levels\n");
|
|
fprintf(stderr," 6 Rec601 YCbCr SD (16-235,240)/255 \"TV\" levels\n");
|
|
fprintf(stderr," 7 Rec709 1125/60Hz YCbCr HD (16-235,240)/255 \"TV\" levels\n");
|
|
fprintf(stderr," 5 Rec709 1250/50Hz YCbCr HD (16-235,240)/255 \"TV\" levels\n");
|
|
fprintf(stderr," 2 Rec2020 YCbCr UHD (16-235,240)/255 \"TV\" levels\n");
|
|
fprintf(stderr," C Rec2020 Constant Luminance YCbCr UHD (16-235,240)/255 \"TV\" levels\n");
|
|
fprintf(stderr," -p separation.%s Use device link separation profile on input\n",ICC_FILE_EXT_ND);
|
|
fprintf(stderr," -E flag Video decode separation device output. See -e above\n");
|
|
fprintf(stderr," -Z nbits Quantize test values to fit in nbits\n");
|
|
fprintf(stderr," -k file.cal Apply calibration (include in .ti3 output)\n");
|
|
fprintf(stderr," -i file.cal Include calibration in .ti3 output, but don't apply it\n");
|
|
fprintf(stderr," -K file.cal Apply inverse calibration\n");
|
|
|
|
fprintf(stderr," -r level Add average random deviation of <level>%% to device values (after sep. & cal.)\n");
|
|
fprintf(stderr," -0 pow Apply power to device chanel 0-9\n");
|
|
fprintf(stderr," -B display.%s Use BT.1886 source EOTF with technical gamma 2.4\n",ICC_FILE_EXT_ND);
|
|
fprintf(stderr," -b g.g:display.%s Use BT.1886-like source EOTF with effective gamma g.g\n",ICC_FILE_EXT_ND);
|
|
fprintf(stderr," -b p.p:g.g:display.%s Use effective gamma g.g source EOTF with p.p prop. output black point offset\n",ICC_FILE_EXT_ND);
|
|
fprintf(stderr," -g g.g:display.%s Use effective gamma g.g source EOTF with all output black point offset\n",ICC_FILE_EXT_ND);
|
|
fprintf(stderr," -I intent r = relative colorimetric, a = absolute (default)\n");
|
|
fprintf(stderr," -A L,a,b Scale black point to target Lab value\n");
|
|
fprintf(stderr," -l Output Lab rather than XYZ\n");
|
|
fprintf(stderr," -s Lookup MPP spectral values\n");
|
|
fprintf(stderr," -R level Add average random deviation of <level>%% to output PCS values\n");
|
|
fprintf(stderr," -u Make random deviations have uniform distributions rather than normal\n");
|
|
fprintf(stderr," -S seed Set random seed\n");
|
|
fprintf(stderr," -U Reverse convert PCS to device, output_r.ti3\n");
|
|
fprintf(stderr," -2 Input .ti2 rather than .ti1\n");
|
|
fprintf(stderr," profile.[%s|mpp|ti3] ICC, MPP profile or TI3 to use\n",ICC_FILE_EXT_ND);
|
|
fprintf(stderr," outfile Base name for input[ti1]/output[ti3] file\n");
|
|
exit(1);
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
int j;
|
|
int fa, nfa, mfa; /* current argument we're looking at */
|
|
int verb = 0; /* Verbose flag */
|
|
int revlookup = 0; /* Do PCS to device space conversion */
|
|
int doti2 = 0; /* Read .ti2 instead of .ti1 */
|
|
int dosep = 0; /* Use separation before profile */
|
|
int bt1886 = 0; /* 1 to apply BT.1886 black point & effective gamma to input */
|
|
/* 2 to apply BT.1886 black point & technical gamma to input */
|
|
double outoprop = 0.0; /* Proportion of black output offset, 0.0 .. 1.0. 0.0 == BT.1886 */
|
|
double egamma = 2.2; /* effective BT.1886 style gamma to ain for */
|
|
double tgamma = 2.4; /* technical BT.1886 style gamma to ain for */
|
|
bt1886_info bt; /* BT.1886 adjustment info */
|
|
int islab = 0; /* Input has Lab rather than XYZ */
|
|
int dolab = 0; /* Output Lab rather than XYZ */
|
|
int gfudge = 0; /* Do grey fudge, 1 = W->RGB, 2 = K->xxxK */
|
|
double chpow[10] = { 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 };
|
|
double rdlevel = 0.0; /* Random device average deviation level (0.0 - 1.0) */
|
|
double rplevel = 0.0; /* Random PCS average deviation level (0.0 - 1.0) */
|
|
int unidist = 0; /* Use uniform distribution of errors */
|
|
unsigned int seed = time(NULL); /* Random seed value */
|
|
double tbp[3] = { -1.0, 0.0, 0.0 }; /* Target black point */
|
|
int applycal = 0; /* 1 to apply calibration, 2 to apply inverse calibration */
|
|
static char sepname[MAXNAMEL+1] = { 0 }; /* ICC separation profile */
|
|
static char calname[MAXNAMEL+1] = { 0 }; /* device calibration */
|
|
static char profname[MAXNAMEL+1] = { 0 }; /* ICC or MPP Profile name */
|
|
static char inname[MAXNAMEL+10+1] = { 0 }; /* Input cgats file base name */
|
|
static char outname[MAXNAMEL+10+1] = { 0 }; /* Output cgats file base name */
|
|
static char odispname[MAXNAMEL+1] = { 0 }; /* BT.1886 display profile name */
|
|
cgats *icg; /* input cgats structure */
|
|
cgats *ocg; /* output cgats structure */
|
|
int nmask = 0; /* Test chart device colorant mask */
|
|
int nchan = 0; /* Test chart number of device chanels */
|
|
int npat; /* Number of patches */
|
|
int si; /* Sample id index */
|
|
int ti; /* Temp index */
|
|
int fi; /* Colorspace index */
|
|
int inti3 = 0; /* Input is a .ti3 format rather than .ti1 */
|
|
|
|
/* TV encode/decode of separation/calibration device link */
|
|
int in_tvenc = 0; /* 1 to use RGB Video Level encoding, 2 = Rec601, etc. */
|
|
int out_tvenc = 0; /* 1 to use RGB Video Level encoding, 2 = Rec601, etc. */
|
|
int qbits = 0; /* Quantization bits, 0 = not set */
|
|
|
|
/* ICC separation/calibration device link profile */
|
|
icmFile *sep_fp = NULL; /* Color profile file */
|
|
icmErr err = { 0, { '\000'} };
|
|
icc *sep_icco = NULL; /* Profile object */
|
|
icmLuSpace *sep_luo = NULL; /* Conversion object */
|
|
icColorSpaceSignature sep_ins, sep_outs; /* Type of input and output spaces */
|
|
int sep_inn; /* Number of input channels to separation */
|
|
inkmask sep_nmask = 0; /* Colorant mask for separation input */
|
|
double wp[3], bp[3]; /* ICC profile Lab white and black points */
|
|
double bpt[3][3]; /* Black point transform matrix (Lab->Lab) */
|
|
|
|
/* Calibration */
|
|
xcal *cal = NULL; /* calibration */
|
|
|
|
/* ICC profile based */
|
|
icRenderingIntent intent = icAbsoluteColorimetric;
|
|
icmFile *icc_fp = NULL; /* Color profile file */
|
|
icc *icc_icco = NULL; /* Profile object */
|
|
icmLuSpace *icc_luo = NULL; /* Conversion object */
|
|
icmLuAlgType alg; /* Algorithm */
|
|
|
|
/* MPP profile based */
|
|
mpp *mlu = NULL; /* Conversion object */
|
|
instType itype = instUnknown; /* Type of instrument used */
|
|
int dospec = 0; /* Do spectral MPP lookup */
|
|
int spec_n = 0; /* Number of spectral bands */
|
|
double spec_wl_short;/* First reading wavelength in nm (shortest) */
|
|
double spec_wl_long; /* Last reading wavelength in nm (longest) */
|
|
|
|
/* TI3 based fake read */
|
|
cgats *ti3 = NULL; /* input cgats structure */
|
|
int ti3_npat = 0; /* Number of patches in reference file */
|
|
int ti3_chix[ICX_MXINKS]; /* Device chanel indexes */
|
|
int ti3_pcsix[3]; /* PCS chanel indexes */
|
|
int ti3_spi[XSPECT_MAX_BANDS]; /* CGATS indexes for each wavelength */
|
|
int ti3_isLab = 0; /* Flag indicating PCS for TI3 file */
|
|
|
|
int rv = 0;
|
|
int inn, outn; /* Number of channels for conversion input, output */
|
|
icColorSpaceSignature ins, outs; /* Type of conversion input and output spaces */
|
|
inkmask cnv_nmask = 0; /* Conversion input nmask */
|
|
time_t clk = time(0);
|
|
struct tm *tsp = localtime(&clk);
|
|
char *atm = asctime(tsp); /* Ascii time */
|
|
char *xyzfname[3] = { "XYZ_X", "XYZ_Y", "XYZ_Z" };
|
|
char *labfname[3] = { "LAB_L", "LAB_A", "LAB_B" };
|
|
|
|
|
|
error_program = "Fakeread";
|
|
if (argc < 3)
|
|
usage("Too few arguments");
|
|
|
|
/* Process the arguments */
|
|
mfa = 2; /* Minimum final arguments */
|
|
for (fa = 1;fa < argc;fa++) {
|
|
nfa = fa; /* skip to nfa if next argument is used */
|
|
if (argv[fa][0] == '-') { /* Look for any flags */
|
|
char *na = NULL; /* next argument after flag, null if none */
|
|
|
|
if (argv[fa][2] != '\000')
|
|
na = &argv[fa][2]; /* next is directly after flag */
|
|
else
|
|
{
|
|
if ((fa+1+mfa) < argc)
|
|
{
|
|
if (argv[fa+1][0] != '-')
|
|
{
|
|
nfa = fa + 1;
|
|
na = argv[nfa]; /* next is seperate non-flag argument */
|
|
}
|
|
}
|
|
}
|
|
|
|
if (argv[fa][1] == '?')
|
|
usage("Usage requested");
|
|
|
|
/* Verbose */
|
|
else if (argv[fa][1] == 'v') {
|
|
verb = 1;
|
|
|
|
if (na != NULL && na[0] >= '0' && na[0] <= '9') {
|
|
verb = atoi(na);
|
|
fa = nfa;
|
|
}
|
|
}
|
|
|
|
/* Spectral MPP lookup */
|
|
else if (argv[fa][1] == 's')
|
|
dospec = 1;
|
|
|
|
/* Video RGB encoding */
|
|
else if (argv[fa][1] == 'e'
|
|
|| argv[fa][1] == 'E') {
|
|
int enc;
|
|
if (na == NULL) usage("Video encodong flag (-e/E) needs an argument");
|
|
switch (na[0]) {
|
|
case 'n': /* Normal */
|
|
enc = 0;
|
|
break;
|
|
case 't': /* TV 16 .. 235 */
|
|
enc = 1;
|
|
break;
|
|
case '6': /* Rec601 YCbCr */
|
|
enc = 2;
|
|
break;
|
|
case '7': /* Rec709 1150/60/2:1 YCbCr */
|
|
enc = 3;
|
|
break;
|
|
case '5': /* Rec709 1250/50/2:1 YCbCr (HD) */
|
|
enc = 4;
|
|
break;
|
|
case '2': /* Rec2020 Non-constant Luminance YCbCr (UHD) */
|
|
enc = 5;
|
|
break;
|
|
case 'C': /* Rec2020 Constant Luminance YCbCr (UHD) */
|
|
enc = 6;
|
|
break;
|
|
default:
|
|
usage("Video encoding (-E) argument not recognised");
|
|
}
|
|
if (argv[fa][1] == 'e')
|
|
in_tvenc = enc;
|
|
else {
|
|
out_tvenc = enc;
|
|
if (qbits == 0)
|
|
qbits = 8;
|
|
}
|
|
fa = nfa;
|
|
}
|
|
|
|
/* Specify quantization bits */
|
|
else if (argv[fa][1] == 'Z') {
|
|
fa = nfa;
|
|
if (na == NULL) usage("Expected argument to -Z");
|
|
qbits = atoi(na);
|
|
if (qbits < 1 || qbits > 32)
|
|
usage("Argument to -Q must be between 1 and 32");
|
|
}
|
|
|
|
/* Separation */
|
|
else if (argv[fa][1] == 'p') {
|
|
dosep = 1;
|
|
|
|
fa = nfa;
|
|
if (na == NULL) usage("Expected an argument to -p");
|
|
strncpy(sepname,na,MAXNAMEL); sepname[MAXNAMEL] = '\000';
|
|
}
|
|
|
|
/* Gamma curve modifier */
|
|
else if (argv[fa][1] == 'b'
|
|
|| argv[fa][1] == 'B'
|
|
|| argv[fa][1] == 'g'
|
|
|| argv[fa][1] == 'G') {
|
|
char *cp;
|
|
|
|
if (na == NULL) usage("Gamma curve flag (-%c) needs an argument",argv[fa][1]);
|
|
|
|
bt1886 = 1; /* Effective */
|
|
if (argv[fa][1] == 'B' || argv[fa][1] == 'G')
|
|
bt1886 = 2; /* Technical */
|
|
|
|
if (argv[fa][1] == 'g' || argv[fa][1] == 'G')
|
|
outoprop = 1.0;
|
|
|
|
/* Grab the filename */
|
|
if ((cp = strrchr(na, ':')) != NULL) {
|
|
double gamma, opr;
|
|
|
|
strncpy(odispname,cp+1,MAXNAMEL); odispname[MAXNAMEL] = '\000';
|
|
*cp = '\000';
|
|
|
|
if (sscanf(na,"%lf:%lf",&opr, &gamma) == 2) {
|
|
outoprop = opr;
|
|
if (bt1886 == 1)
|
|
egamma = gamma;
|
|
else
|
|
tgamma = gamma;
|
|
|
|
} else if (sscanf(na,"%lf",&gamma) == 1) {
|
|
if (bt1886 == 1)
|
|
egamma = gamma;
|
|
else
|
|
tgamma = gamma;
|
|
} else {
|
|
usage("Gamma curve (-%c) arguments not recognised",argv[fa][1]);
|
|
}
|
|
|
|
} else { /* No outoprop or gamma, just filanem */
|
|
strncpy(odispname,na,MAXNAMEL); odispname[MAXNAMEL] = '\000';
|
|
}
|
|
fa = nfa; /* Used argument */
|
|
}
|
|
|
|
/* Lab */
|
|
else if (argv[fa][1] == 'l')
|
|
dolab = 1;
|
|
|
|
/* Uniform distrivuted errors */
|
|
else if (argv[fa][1] == 'u')
|
|
unidist = 1;
|
|
|
|
/* Random seed value */
|
|
else if (argv[fa][1] == 'S') {
|
|
fa = nfa;
|
|
if (na == NULL) usage("Expect argument to -S");
|
|
if (sscanf(na, " %u ",&seed) != 1)
|
|
usage("Couldn't parse argument to -S");
|
|
}
|
|
|
|
/* calibration to device values */
|
|
else if (argv[fa][1] == 'k'
|
|
|| argv[fa][1] == 'K'
|
|
|| argv[fa][1] == 'i') {
|
|
if (argv[fa][1] == 'K')
|
|
applycal = 2;
|
|
else if (argv[fa][1] == 'k')
|
|
applycal = 1;
|
|
else
|
|
applycal = 0;
|
|
fa = nfa;
|
|
if (na == NULL) usage("Expected an argument to -%c",argv[fa][1]);
|
|
strncpy(calname,na,MAXNAMEL); calname[MAXNAMEL] = '\000';
|
|
}
|
|
|
|
/* Extra flags */
|
|
else if (argv[fa][1] == 'Y') {
|
|
fa = nfa;
|
|
if (na == NULL)
|
|
usage("Parameter expected after -Y");
|
|
{
|
|
usage("-Y parameter '%c' not recognised",na[0]);
|
|
}
|
|
}
|
|
|
|
/* Random addition to device levels */
|
|
else if (argv[fa][1] == 'r') {
|
|
fa = nfa;
|
|
if (na == NULL) usage("Expect argument to -r");
|
|
rdlevel = atof(na) * 0.01;
|
|
}
|
|
|
|
/* Power applied to device channels */
|
|
else if (argv[fa][1] >= '0' && argv[fa][1] <= '9') {
|
|
int ch;
|
|
if (na == NULL) {
|
|
if (argv[fa][1] == '2')
|
|
doti2 = 1; /* Read .ti2 instead of .ti1 */
|
|
else
|
|
usage("Expect power argument to -%c",argv[fa][1]);
|
|
} else {
|
|
ch = argv[fa][1]-'0';
|
|
if (ch < 0 || ch > 9)
|
|
usage("Channel no. %d is out of range\n",ch);
|
|
chpow[ch] = atof(na);
|
|
fa = nfa;
|
|
}
|
|
}
|
|
|
|
/* Random addition to PCS levels */
|
|
else if (argv[fa][1] == 'R') {
|
|
fa = nfa;
|
|
if (na == NULL) usage("Expect argument to -R");
|
|
rplevel = atof(na) * 0.01;
|
|
}
|
|
|
|
/* Black point scale */
|
|
else if (argv[fa][1] == 'A') {
|
|
if (na == NULL) usage("Expect argument to -A");
|
|
fa = nfa;
|
|
if (sscanf(na, " %lf , %lf , %lf ",&tbp[0], &tbp[1], &tbp[2]) != 3)
|
|
usage("Couldn't parse argument to -b");
|
|
if (tbp[0] < 0.0 || tbp[0] > 100.0) usage("-b L* value out of range");
|
|
if (tbp[1] < -128.0 || tbp[1] > 128.0) usage("-b a* value out of range");
|
|
if (tbp[2] < -128.0 || tbp[2] > 128.0) usage("-b b* value out of range");
|
|
}
|
|
|
|
/* Intent (only applies to ICC profile) */
|
|
else if (argv[fa][1] == 'I') {
|
|
fa = nfa;
|
|
if (na == NULL) usage("Expect argument to -I");
|
|
switch (na[0]) {
|
|
case 'r':
|
|
intent = icRelativeColorimetric;
|
|
break;
|
|
case 'a':
|
|
intent = icAbsoluteColorimetric;
|
|
break;
|
|
default:
|
|
usage("Unexpected argument value '%c' to -I optyion",na[0]);
|
|
}
|
|
}
|
|
|
|
/* Reverse lookup */
|
|
else if (argv[fa][1] == 'U') {
|
|
revlookup = 1;
|
|
}
|
|
|
|
else
|
|
usage("Unrecognised flag");
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
if (fa >= argc || argv[fa][0] == '-') usage("Missing profile filename argument");
|
|
strncpy(profname,argv[fa++],MAXNAMEL); profname[MAXNAMEL] = '\000';
|
|
|
|
if (fa >= argc || argv[fa][0] == '-') usage("Missing basename argument");
|
|
strncpy(inname,argv[fa],MAXNAMEL-4); inname[MAXNAMEL-4] = '\000';
|
|
if (revlookup)
|
|
strcat(inname,".ti3");
|
|
else {
|
|
if (doti2)
|
|
strcat(inname,".ti2");
|
|
else
|
|
strcat(inname,".ti1");
|
|
}
|
|
strncpy(outname,argv[fa++],MAXNAMEL-4); outname[MAXNAMEL-4] = '\000';
|
|
if (revlookup)
|
|
strcat(outname,"_r");
|
|
strcat(outname,".ti3");
|
|
|
|
|
|
rand32(seed); /* Init seed */
|
|
|
|
if (revlookup && (
|
|
dosep
|
|
|| calname[0] != '\000'
|
|
|| dospec
|
|
|| tbp[0] >= 0.0
|
|
|| bt1886))
|
|
error("Can't do separation, apply calibration, do spectral, black point scale, bt.1886, with reverse lookup");
|
|
|
|
/* Deal with input CGATS files */
|
|
icg = new_cgats(); /* Create a CGATS structure */
|
|
if (doti2)
|
|
icg->add_other(icg, "CTI2"); /* our special input type is Calibration Target Information 1 */
|
|
else
|
|
icg->add_other(icg, "CTI1"); /* our special input type is Calibration Target Information 1 */
|
|
icg->add_other(icg, "CTI3"); /* also accept renamed .ti3 file */
|
|
icg->add_other(icg, "CAL");
|
|
|
|
if (icg->read_name(icg, inname))
|
|
error("CGATS file read error : %s",icg->e.m);
|
|
|
|
if (icg->ntables == 0 || icg->t[0].tt != tt_other || (icg->t[0].oi != 0 && icg->t[0].oi != 1))
|
|
error ("Input file isn't a CTI1 format file");
|
|
if (icg->t[0].oi == 1)
|
|
inti3 = 1; /* It's a renamed .ti3 file */
|
|
if (icg->ntables != 1 && icg->ntables != 2 && icg->ntables != 3)
|
|
error ("Input file doesn't contain one, two or three tables");
|
|
|
|
if ((npat = icg->t[0].nsets) <= 0)
|
|
error ("No sets of data");
|
|
|
|
/* Figure out the color space of the .ti1 */
|
|
if ((fi = icg->find_kword(icg, 0, "COLOR_REP")) < 0)
|
|
error ("Input file doesn't contain keyword COLOR_REP");
|
|
|
|
if (inti3) {
|
|
char *rbuf, *outc;
|
|
|
|
if ((rbuf = strdup(icg->t[0].kdata[fi])) == NULL)
|
|
error("Malloc failed");
|
|
|
|
/* Split COLOR_REP into device and PCS space */
|
|
if ((outc = strchr(rbuf, '_')) == NULL)
|
|
error("Input file '%s' COLOR_REP '%s' invalid", inname, icg->t[0].kdata[fi]);
|
|
*outc++ = '\000';
|
|
|
|
if ((nmask = icx_char2inkmask(rbuf)) == 0) {
|
|
error ("Input file '%s' keyword COLOR_REP has unknown device value '%s'",inname,rbuf);
|
|
}
|
|
|
|
if (strcmp(outc,"LAB") == 0)
|
|
islab = 1;
|
|
else if (strcmp(outc,"XYZ") == 0)
|
|
islab = 0;
|
|
else
|
|
error("Input .ti3 file PCS is neither LAB nor XYZ");
|
|
dolab = islab;
|
|
|
|
free(rbuf);
|
|
} else {
|
|
if ((nmask = icx_char2inkmask(icg->t[0].kdata[fi])) == 0)
|
|
error ("Input file '%s' keyword COLOR_REP has unknown value '%s'",inname, icg->t[0].kdata[fi]);
|
|
}
|
|
|
|
if (revlookup && !inti3) {
|
|
error("reverse lookup expects .ti3 file as input");
|
|
}
|
|
|
|
|
|
/* Deal with separation */
|
|
if (dosep) {
|
|
if ((sep_fp = new_icmFileStd_name(&err,sepname,"r")) == NULL)
|
|
error ("Can't open file '%s' (0x%x, '%s')",sepname,err.c,err.m);
|
|
|
|
if ((sep_icco = new_icc(&err)) == NULL)
|
|
error ("Creation of ICC object failed (0x%x, '%s')",err.c,err.m);
|
|
|
|
/* Deal with ICC separation */
|
|
if ((rv = sep_icco->read(sep_icco,sep_fp,0)) == 0) {
|
|
|
|
/* Get a conversion object */
|
|
if ((sep_luo = (icmLuSpace *)sep_icco->get_luobj(sep_icco, icmFwd, icmDefaultIntent, icmSigDefaultData, icmLuOrdNorm)) == NULL) {
|
|
error ("%d, %s",sep_icco->e.c, sep_icco->e.m);
|
|
}
|
|
|
|
/* Get details of conversion */
|
|
{
|
|
icmCSInfo ini, outi;
|
|
|
|
sep_luo->spaces(sep_luo, &ini, &outi, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
|
sep_ins = ini.sig;
|
|
sep_inn = ini.nch;
|
|
sep_outs = outi.sig;
|
|
}
|
|
sep_nmask = icx_icc_to_colorant_comb(sep_ins, sep_icco->header->deviceClass);
|
|
}
|
|
if (in_tvenc && sep_ins != icSigRgbData)
|
|
error("Can only use TV encoding on sepration RGB input");
|
|
if (out_tvenc && sep_outs != icSigRgbData)
|
|
error("Can only use TV decoding on sepration RGB output");
|
|
} else {
|
|
if (in_tvenc || out_tvenc)
|
|
warning("TV encoding ignored because there is no sepration file");
|
|
}
|
|
|
|
/* Deal with calibration */
|
|
if (calname[0] != '\000') {
|
|
if ((cal = new_xcal()) == NULL)
|
|
error("new_xcal failed");
|
|
if ((cal->read(cal, calname)) != 0)
|
|
error("%s",cal->e.m);
|
|
if (verb) {
|
|
if (applycal == 1)
|
|
printf("Applying calibration curves from '%s'\n",calname);
|
|
else if (applycal == 2)
|
|
printf("Applying inverse calibration curves from '%s'\n",calname);
|
|
else
|
|
printf("Embedding calibration curves from '%s' in output\n",calname);
|
|
}
|
|
}
|
|
|
|
/* If the user hasn't overridden it, get any calibration in the .ti2 */
|
|
if (cal == NULL) { /* No user supplied calibration info */
|
|
int oi, tab;
|
|
|
|
oi = icg->get_oi(icg, "CAL");
|
|
|
|
for (tab = 0; tab < icg->ntables; tab++) {
|
|
if (icg->t[tab].tt == tt_other && icg->t[tab].oi == oi) {
|
|
break;
|
|
}
|
|
}
|
|
if (tab < icg->ntables) {
|
|
if ((cal = new_xcal()) == NULL) {
|
|
error("new_xcal failed");
|
|
}
|
|
if (cal->read_cgats(cal, icg, tab, inname) != 0) {
|
|
error("%s",cal->e.m);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* Deal with ICC profile */
|
|
if ((icc_fp = new_icmFileStd_name(&err,profname,"r")) == NULL)
|
|
error ("Can't open file '%s' (0x%x, '%s')",profname,err.c,err.m);
|
|
|
|
if ((icc_icco = new_icc(&err)) == NULL)
|
|
error ("Creation of ICC object failed (0x%x, '%s')",err.c,err.m);
|
|
|
|
/* Read ICC profile */
|
|
if ((rv = icc_icco->read(icc_icco,icc_fp,0)) == 0) {
|
|
|
|
/* Embed any calibration in the output if it's present */
|
|
if (cal == NULL) {
|
|
applycal = 0;
|
|
cal = xiccReadCalTag(icc_icco);
|
|
|
|
if (verb && cal != NULL)
|
|
printf("Embedding calibration curves from ICC profile in output\n");
|
|
}
|
|
|
|
|
|
if (revlookup) {
|
|
/* Get a PCS to Device conversion object */
|
|
if ((icc_luo = (icmLuSpace *)icc_icco->get_luobj(icc_icco, icmBwd, intent,
|
|
islab ? icSigLabData : icSigXYZData, icmLuOrdNorm)) == NULL) {
|
|
if ((icc_luo = (icmLuSpace *)icc_icco->get_luobj(icc_icco, icmBwd, icmDefaultIntent,
|
|
islab ? icSigLabData : icSigXYZData, icmLuOrdNorm)) == NULL)
|
|
error ("%d, %s",icc_icco->e.c, icc_icco->e.m);
|
|
}
|
|
} else {
|
|
/* Get a Device to PCS conversion object */
|
|
if ((icc_luo = (icmLuSpace *)icc_icco->get_luobj(icc_icco, icmFwd, intent,
|
|
dolab ? icSigLabData : icSigXYZData, icmLuOrdNorm)) == NULL) {
|
|
if ((icc_luo = (icmLuSpace *)icc_icco->get_luobj(icc_icco, icmFwd, icmDefaultIntent,
|
|
dolab ? icSigLabData : icSigXYZData, icmLuOrdNorm)) == NULL)
|
|
error ("%d, %s",icc_icco->e.c, icc_icco->e.m);
|
|
}
|
|
}
|
|
|
|
/* Get details of conversion */
|
|
{
|
|
icmCSInfo ini, outi;
|
|
|
|
icc_luo->spaces(icc_luo, &ini, &outi, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
|
ins = ini.sig;
|
|
inn = ini.nch;
|
|
outs = outi.sig;
|
|
outn = outi.nch;
|
|
|
|
alg = icmSynthAlgType((icmLuBase *)icc_luo);
|
|
}
|
|
|
|
cnv_nmask = icx_icc_to_colorant_comb(ins, icc_icco->header->deviceClass);
|
|
|
|
if (dospec)
|
|
error("Can't lookup spectral values for ICC profile");
|
|
|
|
if (tbp[0] >= 0.0) {
|
|
double ss[3], tt[3];
|
|
|
|
icc_luo->wh_bk_points(icc_luo, wp, bp, NULL);
|
|
if (dolab) {
|
|
icmXYZ2Lab(&icmD50, wp, wp);
|
|
icmXYZ2Lab(&icmD50, bp, bp);
|
|
} else {
|
|
icmLab2XYZ(&icmD50, tbp, tbp);
|
|
}
|
|
|
|
/* Create scaling matrix */
|
|
for (j = 0; j < 3; j++) {
|
|
tt[j] = tbp[j] - wp[j];
|
|
ss[j] = bp[j] - wp[j];
|
|
}
|
|
icmRotMat(bpt, ss, tt);
|
|
|
|
if (verb) {
|
|
printf("White point = %f %f %f (%s)\n",wp[0],wp[1],wp[2], dolab ? "Lab" : "XYZ");
|
|
printf("Black point = %f %f %f (%s)\n",bp[0],bp[1],bp[2], dolab ? "Lab" : "XYZ");
|
|
printf("Target Black point = %f %f %f (%s)\n",tbp[0],tbp[1],tbp[2], dolab ? "Lab" : "XYZ");
|
|
}
|
|
}
|
|
|
|
} else { /* Not a valid ICC */
|
|
/* Close out the ICC profile */
|
|
icc_icco->del(icc_icco);
|
|
icc_icco = NULL;
|
|
icc_fp->del(icc_fp);
|
|
icc_fp = NULL;
|
|
icc_luo = NULL;
|
|
}
|
|
|
|
/* If we don't have an ICC lookup object, look for an MPP */
|
|
if (icc_luo == NULL) {
|
|
|
|
if (revlookup)
|
|
error("Reverse lookup using MPP not supported");
|
|
|
|
if ((mlu = new_mpp()) == NULL)
|
|
error ("Creation of MPP object failed");
|
|
|
|
if ((rv = mlu->read_mpp(mlu, profname)) == 0) {
|
|
|
|
/* mlu defaults to absolute XYZ lookup */
|
|
if (dolab) {
|
|
mlu->set_ilob(mlu, icxIT_default, NULL, icxOT_default, NULL, icSigLabData, 0);
|
|
}
|
|
|
|
mlu->get_info(mlu, &cnv_nmask, &inn, NULL,
|
|
&spec_n, &spec_wl_short, &spec_wl_long, &itype, NULL);
|
|
|
|
outn = 3;
|
|
outs = dolab ? icSigLabData : icSigXYZData;
|
|
|
|
if ((ins = icx_colorant_comb_to_icc(cnv_nmask)) == 0)
|
|
error ("Couldn't match MPP mask to valid ICC colorspace");
|
|
|
|
if (dospec && spec_n <= 0)
|
|
error("Can't lookup spectral values for non-spectral MPP profile");
|
|
} else {
|
|
mlu->del(mlu);
|
|
mlu = NULL;
|
|
}
|
|
}
|
|
|
|
/* If we don't have an ICC or MPP lookup object, look for a TI3 */
|
|
if (icc_luo == NULL && mlu == NULL) {
|
|
char *rbuf, *outc;
|
|
char *ti3_bident;
|
|
int ti3_nchan;
|
|
|
|
if (revlookup)
|
|
error("Reverse lookup using TI3 as conversion not supported");
|
|
|
|
ti3 = new_cgats(); /* Create a CGATS structure */
|
|
ti3->add_other(ti3, "CTI3");/* our special input type is Calibration Target Information 3 */
|
|
|
|
if (ti3->read_name(ti3, profname))
|
|
error("CGATS file read error : %s",ti3->e.m);
|
|
|
|
if (ti3->ntables == 0 || ti3->t[0].tt != tt_other || ti3->t[0].oi != 0)
|
|
error ("Profile file '%s' isn't a CTI3 format file",profname);
|
|
if (ti3->ntables != 1)
|
|
error ("Input file '%s' doesn't contain one table",profname);
|
|
|
|
if ((ti = ti3->find_kword(ti3, 0, "COLOR_REP")) < 0)
|
|
error("Input file doesn't contain keyword COLOR_REP");
|
|
|
|
if ((rbuf = strdup(ti3->t[0].kdata[ti])) == NULL)
|
|
error("Malloc failed");
|
|
|
|
/* Split COLOR_REP into device and PCS space */
|
|
if ((outc = strchr(rbuf, '_')) == NULL)
|
|
error("COLOR_REP '%s' invalid", ti3->t[0].kdata[ti]);
|
|
*outc++ = '\000';
|
|
|
|
outn = 3;
|
|
if (strcmp(outc, "XYZ") == 0) {
|
|
ti3_isLab = 0;
|
|
outs = icSigXYZData;
|
|
} else if (strcmp(outc, "LAB") == 0) {
|
|
ti3_isLab = 1;
|
|
outs = icSigLabData;
|
|
} else
|
|
error("COLOR_REP '%s' invalid (Neither XYZ nor LAB)", ti3->t[0].kdata[ti]);
|
|
|
|
if ((cnv_nmask = icx_char2inkmask(rbuf)) == 0) {
|
|
error ("File '%s' keyword COLOR_REP has unknown device value '%s'",profname,rbuf);
|
|
}
|
|
|
|
free(rbuf);
|
|
|
|
if ((ins = icx_colorant_comb_to_icc(cnv_nmask)) == 0)
|
|
error ("Couldn't match MPP mask to valid ICC colorspace");
|
|
|
|
if ((inn = icmCSSig2nchan(ins)) == 0)
|
|
error ("TI3 Colorspace with unknown number of channels");
|
|
|
|
if ((ti3_npat = ti3->t[0].nsets) <= 0)
|
|
error ("No sets of data in reference TI3 file");
|
|
|
|
ti3_nchan = icx_noofinks(cnv_nmask);
|
|
ti3_bident = icx_inkmask2char(cnv_nmask, 0);
|
|
|
|
/* Find device fields */
|
|
for (j = 0; j < ti3_nchan; j++) {
|
|
int ii, imask;
|
|
char fname[100];
|
|
|
|
imask = icx_index2ink(cnv_nmask, j);
|
|
sprintf(fname,"%s_%s",cnv_nmask == ICX_W || cnv_nmask == ICX_K ? "GRAY" : ti3_bident,
|
|
icx_ink2char(imask));
|
|
|
|
if ((ii = ti3->find_field(ti3, 0, fname)) < 0)
|
|
error ("Input file doesn't contain field %s",fname);
|
|
if (ti3->t[0].ftype[ii] != r_t)
|
|
error ("Field %s is wrong type - expect float",fname);
|
|
ti3_chix[j] = ii;
|
|
}
|
|
|
|
/* Find PCS fields */
|
|
for (j = 0; j < 3; j++) {
|
|
int ii;
|
|
|
|
if ((ii = ti3->find_field(ti3, 0, ti3_isLab ? labfname[j] : xyzfname[j])) < 0)
|
|
error ("Input file doesn't contain field %s",ti3_isLab ? labfname[j] : xyzfname[j]);
|
|
if (ti3->t[0].ftype[ii] != r_t)
|
|
error ("Field %s is wrong type - expect float",ti3_isLab ? labfname[j] : xyzfname[j]);
|
|
ti3_pcsix[j] = ii;
|
|
}
|
|
|
|
/* Find spectral fields */
|
|
if ((ti = ti3->find_kword(ti3, 0, "SPECTRAL_BANDS")) >= 0) {
|
|
char buf[100];
|
|
|
|
spec_n = atoi(ti3->t[0].kdata[ti]);
|
|
if ((ti = ti3->find_kword(ti3, 0, "SPECTRAL_START_NM")) < 0)
|
|
error ("Input file doesn't contain keyword SPECTRAL_START_NM");
|
|
spec_wl_short = atof(ti3->t[0].kdata[ti]);
|
|
if ((ti = ti3->find_kword(ti3, 0, "SPECTRAL_END_NM")) < 0)
|
|
error ("Input file doesn't contain keyword SPECTRAL_END_NM");
|
|
spec_wl_long = atof(ti3->t[0].kdata[ti]);
|
|
|
|
/* Find the fields for spectral values */
|
|
for (j = 0; j < spec_n; j++) {
|
|
int nm;
|
|
|
|
/* Compute nearest integer wavelength */
|
|
nm = (int)(spec_wl_short + ((double)j/(spec_n-1.0))
|
|
* (spec_wl_long - spec_wl_short) + 0.5);
|
|
|
|
sprintf(buf,"SPEC_%03d",nm);
|
|
|
|
if ((ti3_spi[j] = ti3->find_field(ti3, 0, buf)) < 0)
|
|
error("Input file doesn't contain field %s",buf);
|
|
|
|
if (ti3->t[0].ftype[ti3_spi[j]] != r_t)
|
|
error("Field %s is wrong type - expect float",buf);
|
|
}
|
|
}
|
|
|
|
if (dospec && spec_n <= 0)
|
|
error("Can't lookup spectral values for non-spectral TI3 file");
|
|
|
|
free(ti3_bident);
|
|
}
|
|
|
|
/* Dealy with BT.1886 processing */
|
|
if (bt1886) {
|
|
icmFile *ofp = NULL; /* Output Color profile file */
|
|
icc *oicco = NULL; /* Output Profile object */
|
|
icmLuSpace *oluo = NULL; /* Output Conversion object */
|
|
icmLuMatrix *lu; /* Input profile lookup */
|
|
double bp[3], rgb[3];
|
|
|
|
if (icc_luo == NULL)
|
|
error ("Can only use BT.1886 with an ICC profile");
|
|
|
|
/* Check input profile is an RGB matrix profile */
|
|
if (alg != icmMatrixFwdType
|
|
|| ins != icSigRgbData)
|
|
error("BT.1886 mode only works with an RGB matrix input profile");
|
|
|
|
lu = (icmLuMatrix *)icc_luo; /* Safe to coerce - we have checked it's matrix. */
|
|
|
|
/* Open up output profile used for BT.1886 black point */
|
|
if ((ofp = new_icmFileStd_name(&err,odispname,"r")) == NULL)
|
|
error ("Can't open file '%s' (0x%x, '%s')",odispname,err.c,err.m);
|
|
|
|
if ((oicco = new_icc(&err)) == NULL)
|
|
error ("Creation of ICC object failed (0x%x, '%s')",err.c,err.m);
|
|
|
|
if (oicco->read(oicco,ofp,0))
|
|
error ("Unable to read '%s'",odispname);
|
|
|
|
if ((oluo = (icmLuSpace *)oicco->get_luobj(oicco, icmFwd, icRelativeColorimetric,
|
|
icSigXYZData, icmLuOrdNorm)) == NULL)
|
|
error("get output ICC lookup object failed: %d, %s",oicco->e.c,oicco->e.m);
|
|
|
|
/* We're assuming that the input space has a perfect black point... */
|
|
|
|
/* Lookup the output black point in XYZ PCS. We're assuming monotonicity.. */
|
|
bp[0] = bp[1] = bp[2] = 0.0;
|
|
oluo->lookup_fwd(oluo, bp, bp);
|
|
|
|
/* Done with output profile */
|
|
oluo->del(oluo);
|
|
oicco->del(oicco);
|
|
ofp->del(ofp);
|
|
|
|
bt1886_setup(&bt, &lu->pcswht, bp, outoprop,
|
|
bt1886 == 1 ? egamma : tgamma, bt1886 == 1 ? 1 : 0);
|
|
|
|
if (verb)
|
|
printf("Gamma Curve: Using output black offset proportion %f\n",outoprop);
|
|
|
|
if (bt1886 == 1) { /* Using effective gamma */
|
|
if (verb)
|
|
printf("Gamma Curve: Technical gamma %f used to achieve effective gamma %f\n",
|
|
bt.gamma, egamma);
|
|
} else {
|
|
if (verb)
|
|
printf("Gamma Curve: Using technical gamma %f\n",bt.gamma);
|
|
}
|
|
|
|
|
|
if (verb) {
|
|
printf("Gamma Curve: target out black rel XYZ = %f %f %f, Lab %f %f %f\n",
|
|
bp[0],bp[1],bp[2], bt.outL, bt.tab[0], bt.tab[1]);
|
|
printf("Gamma Curve: Y input offset = %f\n", bt.ingo);
|
|
printf("Gamma Curve: Y output scale = %f\n", bt.outsc);
|
|
printf("Gamma Curve: Y output offset = %f\n", bt.outo);
|
|
}
|
|
|
|
/* Check black point now produced by input profile with bt.1886 adjustment */
|
|
rgb[0] = rgb[1] = rgb[2] = 0.0;
|
|
bt1886_fwd_curve(&bt, rgb, rgb);
|
|
lu->core5_fwd(lu, rgb, rgb);
|
|
bt1886_wp_adjust(&bt, rgb, rgb);
|
|
if (verb) printf("BT.1886: check input black point rel. XYZ %f %f %f\n", rgb[0],rgb[1],rgb[2]);
|
|
if (verb > 1) {
|
|
int i, no = 21;
|
|
|
|
/* Overral rendering curve from video in to output target */
|
|
printf("BT.1886: overall rendering\n");
|
|
for (i = 0; i < no; i++) {
|
|
double v = i/(no-1.0), vv;
|
|
double vi[3], vo[3], Lab[3];
|
|
double loglog = 0.0;
|
|
|
|
vi[0] = vi[1] = vi[2] = v;
|
|
bt1886_fwd_curve(&bt, vo, vi);
|
|
lu->core5_fwd(lu, vo, vo);
|
|
bt1886_wp_adjust(&bt, vo, vo);
|
|
|
|
icmXYZ2Lab(&lu->pcswht, Lab, vo);
|
|
|
|
if (v > 1e-9 && vo[1] > 1e-9 && fabs(v - 1.0) > 1e-9)
|
|
loglog = log(vo[1])/log(v);
|
|
|
|
printf(" In %5.1f%% -> XYZ in %f -> bt.1886 %f, log/log %.3f, Lab %f %f %f \n",v * 100.0,vi[1],vo[1], loglog, Lab[0], Lab[1], Lab[2]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Some sanity checking */
|
|
if (tbp[0] >= 0.0 && icc_luo == NULL)
|
|
error("Black scaling only works with ICC profile");
|
|
|
|
if (verb) {
|
|
// printf("Random seed is %u\n",seed);
|
|
|
|
if (rdlevel > 0.0)
|
|
printf("Adding %.3f%% average deviation %s noise to device values\n",rdlevel * 100.0,unidist ? "uniform" : "normal");
|
|
for (j = 0; j < inn && j < 10; j++) {
|
|
if (chpow[j] != 1.0)
|
|
printf("Applying chan %d power %f\n",j,chpow[j]);
|
|
}
|
|
if (rplevel > 0.0)
|
|
printf("Adding %.3f%% average deviation %s noise to %s PCS values\n",rplevel * 100.0,unidist ? "uniform" : "normal", dolab ? "L*a*b*": "XYZ");
|
|
fflush(stdout);
|
|
}
|
|
|
|
/* Setup output cgats file */
|
|
ocg = new_cgats(); /* Create a CGATS structure */
|
|
ocg->add_other(ocg, "CTI3"); /* our special type is Calibration Target Information 3 */
|
|
ocg->add_table(ocg, tt_other, 0); /* Start the first table */
|
|
|
|
ocg->add_kword(ocg, 0, "DESCRIPTOR", "Argyll Calibration Target chart information 3",NULL);
|
|
ocg->add_kword(ocg, 0, "ORIGINATOR", "Argyll fakeread", NULL);
|
|
atm[strlen(atm)-1] = '\000'; /* Remove \n from end */
|
|
ocg->add_kword(ocg, 0, "CREATED",atm, NULL);
|
|
|
|
/* What sort of device this is */
|
|
if (ti3 != NULL) {
|
|
if ((ti = ti3->find_kword(ti3, 0, "DEVICE_CLASS")) < 0)
|
|
error("Input TI3 doesn't contain keyword DEVICE_CLASS");
|
|
ocg->add_kword(ocg, 0, "DEVICE_CLASS",ti3->t[0].kdata[ti], NULL); /* Copy */
|
|
} else if (mlu != NULL) {
|
|
/* This is a guess. It may not be correct. */
|
|
if ((cnv_nmask & ICX_ADDITIVE) ^ (cnv_nmask & ICX_INVERTED)) {
|
|
ocg->add_kword(ocg, 0, "DEVICE_CLASS","DISPLAY", NULL);
|
|
if (nmask == ICX_IRGB)
|
|
warning("It's unusual to have an iRGB display device !");
|
|
} else {
|
|
ocg->add_kword(ocg, 0, "DEVICE_CLASS","OUTPUT", NULL);
|
|
if (nmask == ICX_RGB)
|
|
warning("It's unusual to have an RGB printing device !");
|
|
}
|
|
} else { /* Assume ICC */
|
|
if (icc_icco->header->deviceClass == icSigDisplayClass) {
|
|
ocg->add_kword(ocg, 0, "DEVICE_CLASS","DISPLAY", NULL);
|
|
if (nmask == ICX_IRGB)
|
|
warning("It's unusual to have an iRGB display device !");
|
|
} else {
|
|
ocg->add_kword(ocg, 0, "DEVICE_CLASS","OUTPUT", NULL);
|
|
if (nmask == ICX_RGB)
|
|
warning("It's unusual to have an RGB printing device !");
|
|
}
|
|
}
|
|
|
|
if ((ti = icg->find_kword(icg, 0, "SINGLE_DIM_STEPS")) >= 0)
|
|
ocg->add_kword(ocg, 0, "SINGLE_DIM_STEPS",icg->t[0].kdata[ti], NULL);
|
|
|
|
if ((ti = icg->find_kword(icg, 0, "COMP_GREY_STEPS")) >= 0)
|
|
ocg->add_kword(ocg, 0, "COMP_GREY_STEPS",icg->t[0].kdata[ti], NULL);
|
|
|
|
if ((ti = icg->find_kword(icg, 0, "MULTI_DIM_STEPS")) >= 0)
|
|
ocg->add_kword(ocg, 0, "MULTI_DIM_STEPS",icg->t[0].kdata[ti], NULL);
|
|
|
|
if ((ti = icg->find_kword(icg, 0, "FULL_SPREAD_PATCHES")) >= 0)
|
|
ocg->add_kword(ocg, 0, "FULL_SPREAD_PATCHES",icg->t[0].kdata[ti], NULL);
|
|
|
|
if ((ti = icg->find_kword(icg, 0, "DARK_REGION_EMPHASIS")) >= 0)
|
|
ocg->add_kword(ocg, 0, "DARK_REGION_EMPHASIS",icg->t[0].kdata[ti], NULL);
|
|
|
|
if (icc_luo == NULL) { /* If MPP profile, we know what the target instrument is */
|
|
ocg->add_kword(ocg, 0, "TARGET_INSTRUMENT", inst_name(itype) , NULL);
|
|
}
|
|
|
|
/* Fields we want */
|
|
ocg->add_field(ocg, 0, "SAMPLE_ID", nqcs_t);
|
|
|
|
if ((si = icg->find_field(icg, 0, "SAMPLE_ID")) < 0)
|
|
error ("Input file doesn't contain field SAMPLE_ID");
|
|
|
|
{
|
|
int i, j, ii;
|
|
int chix[ICX_MXINKS]; /* Device chanel indexes */
|
|
int pcsix[3]; /* PCS chanel indexes (for revlookup) */
|
|
char *ident, *bident;
|
|
int nsetel = 0;
|
|
cgats_set_elem *setel; /* Array of set value elements */
|
|
|
|
nchan = icx_noofinks(nmask); /* No. device channels */
|
|
ident = icx_inkmask2char(nmask, 1);
|
|
bident = icx_inkmask2char(nmask, 0);
|
|
|
|
/* Sanity check what we're going to do */
|
|
if (dosep) {
|
|
|
|
/* Check if sep ICC input is compatible with .ti1 */
|
|
if (nmask == ICX_W && sep_ins == icSigRgbData)
|
|
gfudge = 1;
|
|
else if (nmask == ICX_K && sep_ins == icSigCmykData)
|
|
gfudge = 2;
|
|
else if (icx_colorant_comb_match_icc(nmask, sep_ins) == 0) {
|
|
error("Separation ICC device space '%s' doesn't match TI1 '%s'",
|
|
icm2str(icmColorSpaceSig, sep_ins),
|
|
ident); /* Should free(). */
|
|
}
|
|
|
|
/* Check if separation ICC output is compatible with ICC/MPP/TI3 conversion */
|
|
if (icc_luo != NULL) {
|
|
/* Check if icc is compatible with .ti1 */
|
|
if (sep_outs != ins)
|
|
error("ICC device space '%s' doesn't match Separation ICC '%s'",
|
|
icm2str(icmColorSpaceSig, ins),
|
|
icm2str(icmColorSpaceSig, sep_outs));
|
|
} else if (mlu != NULL) {
|
|
/* Check if mpp is compatible with .ti1 */
|
|
if (icx_colorant_comb_match_icc(cnv_nmask, sep_outs) == 0)
|
|
error("MPP device space '%s' doesn't match Separation ICC '%s'",
|
|
icx_inkmask2char(cnv_nmask, 1), /* Should free(). */
|
|
icm2str(icmColorSpaceSig, sep_outs));
|
|
} else if (ti3 != NULL) {
|
|
/* Check if .ti3 is compatible with .ti1 */
|
|
if (icx_colorant_comb_match_icc(cnv_nmask, sep_outs) == 0)
|
|
error("TI3 device space '%s' doesn't match Separation ICC '%s'",
|
|
icx_inkmask2char(cnv_nmask, 1), /* Should free(). */
|
|
icm2str(icmColorSpaceSig, sep_outs));
|
|
}
|
|
} else if (icc_luo != NULL) {
|
|
/* Check if icc is compatible with .ti1 */
|
|
if (nmask == ICX_W && ins == icSigRgbData)
|
|
gfudge = 1;
|
|
else if (nmask == ICX_K && ins == icSigCmykData)
|
|
gfudge = 2; /* Should allow for other colorant combo's that include black */
|
|
else {
|
|
if (!revlookup) {
|
|
if (icx_colorant_comb_match_icc(nmask, ins) == 0)
|
|
error("ICC device space '%s' doesn't match TI1 '%s'",
|
|
icm2str(icmColorSpaceSig, ins),
|
|
ident); // Should free().
|
|
} else {
|
|
if (icx_colorant_comb_match_icc(nmask, outs) == 0)
|
|
error("ICC device space '%s' doesn't match TI1 '%s'",
|
|
icm2str(icmColorSpaceSig, ins),
|
|
ident); // Should free().
|
|
|
|
}
|
|
}
|
|
} else if (mlu != NULL) {
|
|
/* Check if mpp is compatible with .ti1 */
|
|
if (nmask == ICX_W && (cnv_nmask == ICX_RGB || cnv_nmask == ICX_IRGB))
|
|
gfudge = 1;
|
|
else if (nmask == ICX_K && (cnv_nmask & ICX_BLACK))
|
|
gfudge = 2;
|
|
else if (cnv_nmask != nmask)
|
|
error("MPP device space '%s' doesn't match TI1 '%s'",
|
|
icx_inkmask2char(cnv_nmask, 1), ident); // Should free().
|
|
} else if (ti3 != NULL) {
|
|
/* Check if .ti3 is compatible with .ti1 */
|
|
if (nmask == ICX_W && (cnv_nmask == ICX_RGB || cnv_nmask == ICX_IRGB))
|
|
gfudge = 1;
|
|
else if (nmask == ICX_K && (cnv_nmask & ICX_BLACK))
|
|
gfudge = 2;
|
|
else if (cnv_nmask != nmask)
|
|
error("TI3 device space '%s' doesn't match TI1 '%s'",
|
|
icx_inkmask2char(cnv_nmask, 1), ident); // Should free().
|
|
}
|
|
|
|
if ((ii = icg->find_kword(icg, 0, "TOTAL_INK_LIMIT")) >= 0)
|
|
ocg->add_kword(ocg, 0, "TOTAL_INK_LIMIT",icg->t[0].kdata[ii], NULL);
|
|
if ((ii = icg->find_kword(icg, 0, "FINAL_TOTAL_INK_LIMIT")) >= 0)
|
|
ocg->add_kword(ocg, 0, "FINAL_TOTAL_INK_LIMIT",icg->t[0].kdata[ii], NULL);
|
|
if ((ii = icg->find_kword(icg, 0, "TEXT_SPACER_TOTAL_INK_LIMIT")) >= 0)
|
|
ocg->add_kword(ocg, 0, "TEXT_SPACER_TOTAL_INK_LIMIT",icg->t[0].kdata[ii], NULL);
|
|
if ((ii = icg->find_kword(icg, 0, "FINAL_TEXT_SPACER_TOTAL_INK_LIMIT")) >= 0)
|
|
ocg->add_kword(ocg, 0, "FINAL_TEXT_SPACER_TOTAL_INK_LIMIT",icg->t[0].kdata[ii], NULL);
|
|
|
|
nsetel += 1; /* For id */
|
|
nsetel += nchan; /* For device values */
|
|
nsetel += 3; /* For XYZ/Lab */
|
|
|
|
/* Locate device fields in source file, and add to output file */
|
|
for (j = 0; j < nchan; j++) {
|
|
int imask;
|
|
char fname[100];
|
|
|
|
imask = icx_index2ink(nmask, j);
|
|
sprintf(fname,"%s_%s",nmask == ICX_W || nmask == ICX_K ? "GRAY" : bident,
|
|
icx_ink2char(imask));
|
|
|
|
if ((ii = icg->find_field(icg, 0, fname)) < 0)
|
|
error ("Input file doesn't contain field %s",fname);
|
|
if (icg->t[0].ftype[ii] != r_t)
|
|
error ("Field %s is wrong type - expect float",fname);
|
|
|
|
ocg->add_field(ocg, 0, fname, r_t);
|
|
chix[j] = ii;
|
|
}
|
|
|
|
/* Find PCS fields if doing reverse lookup */
|
|
if (revlookup) {
|
|
for (j = 0; j < 3; j++) {
|
|
int ii;
|
|
|
|
if ((ii = icg->find_field(icg, 0, islab ? labfname[j] : xyzfname[j])) < 0)
|
|
error ("Input file doesn't contain field %s",islab ? labfname[j] : xyzfname[j]);
|
|
if (icg->t[0].ftype[ii] != r_t)
|
|
error ("Field %s is wrong type - expect float",islab ? labfname[j] : xyzfname[j]);
|
|
pcsix[j] = ii;
|
|
}
|
|
}
|
|
|
|
/* Add PCS fields */
|
|
for (j = 0; j < 3; j++) {
|
|
ocg->add_field(ocg, 0, dolab ? labfname[j] : xyzfname[j], r_t);
|
|
}
|
|
|
|
/* If we have spectral information, output it too */
|
|
if (dospec > 0 && spec_n > 0) {
|
|
char buf[100];
|
|
|
|
nsetel += spec_n; /* Spectral values */
|
|
sprintf(buf,"%d", spec_n);
|
|
ocg->add_kword(ocg, 0, "SPECTRAL_BANDS",buf, NULL);
|
|
sprintf(buf,"%f", spec_wl_short);
|
|
ocg->add_kword(ocg, 0, "SPECTRAL_START_NM",buf, NULL);
|
|
sprintf(buf,"%f", spec_wl_long);
|
|
ocg->add_kword(ocg, 0, "SPECTRAL_END_NM",buf, NULL);
|
|
|
|
/* Generate fields for spectral values */
|
|
for (i = 0; i < spec_n; i++) {
|
|
int nm;
|
|
|
|
/* Compute nearest integer wavelength */
|
|
nm = (int)(spec_wl_short + ((double)i/(spec_n-1.0))
|
|
* (spec_wl_long - spec_wl_short) + 0.5);
|
|
|
|
sprintf(buf,"SPEC_%03d",nm);
|
|
ocg->add_field(ocg, 0, buf, r_t);
|
|
}
|
|
}
|
|
|
|
{
|
|
char fname[100];
|
|
sprintf(fname, dolab ? "%s_LAB" : "%s_XYZ", ident);
|
|
ocg->add_kword(ocg, 0, "COLOR_REP", fname, NULL);
|
|
}
|
|
|
|
if ((setel = (cgats_set_elem *)malloc(sizeof(cgats_set_elem) * nsetel)) == NULL)
|
|
error("Malloc failed!");
|
|
|
|
/* Read all the device test patches in, convert them to PCS, */
|
|
/* and write them out. */
|
|
if (!revlookup) {
|
|
for (i = 0; i < npat; i++) {
|
|
int k = 0;
|
|
char *id;
|
|
double odev[ICX_MXINKS], dev[ICX_MXINKS], sep[ICX_MXINKS], PCS[3];
|
|
xspect out;
|
|
double qscale = (1 << qbits) - 1.0;
|
|
|
|
for (j = 0; j < nchan; j++) {
|
|
double dv = *((double *)icg->t[0].fdata[i][chix[j]]) / 100.0;
|
|
if (qbits > 0) {
|
|
double vr;
|
|
dv *= qscale;
|
|
vr = floor(dv + 0.5);
|
|
if ((vr - dv) == 0.5 && (((int)vr) & 1) != 0) /* Round to even */
|
|
vr -= 1.0;
|
|
dv = vr/qscale;
|
|
}
|
|
odev[j] = dev[j] = sep[j] = dv;
|
|
}
|
|
|
|
if (gfudge) {
|
|
int nch;
|
|
|
|
if (dosep) /* Figure number of channels into conversion */
|
|
nch = sep_inn;
|
|
else
|
|
nch = inn;
|
|
|
|
if (gfudge == 1) { /* Convert W -> RGB */
|
|
double wval = dev[0];
|
|
for (j = 0; j < nch; j++)
|
|
dev[j] = sep[j] = wval;
|
|
|
|
} else { /* Convert K->xxxK */
|
|
int kch;
|
|
int inmask;
|
|
double kval = dev[0];
|
|
|
|
if (dosep) /* Figure number of channels into conversion */
|
|
inmask = sep_nmask;
|
|
else
|
|
inmask = cnv_nmask;
|
|
|
|
if (inmask == 0)
|
|
error("Input colorspace ambiguous - can't determine if it has black");
|
|
|
|
if ((kch = icx_ink2index(inmask, ICX_BLACK)) == -1)
|
|
error("Can't find black colorant for K fudge");
|
|
for (j = 0; j < nch; j++) {
|
|
if (j == kch)
|
|
dev[j] = sep[j] = kval;
|
|
else
|
|
dev[j] = sep[j] = 0.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (dosep) {
|
|
if (in_tvenc != 0) {
|
|
if (in_tvenc == 1) { /* Video 16-235 range */
|
|
icmRGB_2_VidRGB(dev, dev);
|
|
} else if (in_tvenc == 2) { /* Rec601 YCbCr */
|
|
icmRec601_RGBd_2_YPbPr(dev, dev);
|
|
icmRecXXX_YPbPr_2_YCbCr(dev, dev);
|
|
} else if (in_tvenc == 3) { /* Rec709 YCbCr */
|
|
icmRec709_RGBd_2_YPbPr(dev, dev);
|
|
icmRecXXX_YPbPr_2_YCbCr(dev, dev);
|
|
} else if (out_tvenc == 4) { /* Rec709 1250/50/2:1 YCbCr */
|
|
icmRec709_50_RGBd_2_YPbPr(dev, dev);
|
|
icmRecXXX_YPbPr_2_YCbCr(dev, dev);
|
|
} else if (out_tvenc == 5) { /* Rec2020 Non-constant Luminance YCbCr */
|
|
icmRec2020_NCL_RGBd_2_YPbPr(dev, dev);
|
|
icmRecXXX_YPbPr_2_YCbCr(dev, dev);
|
|
} else if (out_tvenc == 6) { /* Rec2020 Non-constant Luminance YCbCr */
|
|
icmRec2020_CL_RGBd_2_YPbPr(dev, dev);
|
|
icmRecXXX_YPbPr_2_YCbCr(dev, dev);
|
|
}
|
|
}
|
|
|
|
if (sep_luo->lookup_fwd(sep_luo, sep, dev) & icmPe_lurv_err)
|
|
error ("%d, %s",icc_icco->e.c,icc_icco->e.m);
|
|
|
|
if (out_tvenc != 0) {
|
|
if (out_tvenc == 1) { /* Video 16-235 range */
|
|
icmVidRGB_2_RGB(sep, sep);
|
|
} else if (out_tvenc == 2) { /* Rec601 YCbCr */
|
|
icmRecXXX_YCbCr_2_YPbPr(sep, sep);
|
|
icmRec601_YPbPr_2_RGBd(sep, sep);
|
|
} else if (out_tvenc == 3) { /* Rec709 1150/60/2:1 YCbCr */
|
|
icmRecXXX_YCbCr_2_YPbPr(sep, sep);
|
|
icmRec709_YPbPr_2_RGBd(sep, sep);
|
|
} else if (out_tvenc == 4) { /* Rec709 1250/50/2:1 YCbCr */
|
|
icmRecXXX_YCbCr_2_YPbPr(sep, sep);
|
|
icmRec709_50_YPbPr_2_RGBd(sep, sep);
|
|
} else if (out_tvenc == 5) { /* Rec2020 Non-constant Luminance YCbCr */
|
|
icmRecXXX_YCbCr_2_YPbPr(sep, sep);
|
|
icmRec2020_NCL_YPbPr_2_RGBd(sep, sep);
|
|
} else if (out_tvenc == 6) { /* Rec2020 Non-constant Luminance YCbCr */
|
|
icmRecXXX_YCbCr_2_YPbPr(sep, sep);
|
|
icmRec2020_CL_YPbPr_2_RGBd(sep, sep);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Do calibration */
|
|
if (applycal && cal != NULL) {
|
|
if (applycal == 1)
|
|
cal->interp(cal, sep, sep);
|
|
else if (applycal == 2) {
|
|
if (cal->inv_interp(cal, sep, sep))
|
|
warning("Inverse calibration of patch %d failed",i+1);
|
|
}
|
|
}
|
|
|
|
/* Add randomness and non-linearity to device values. */
|
|
/* rdlevel = avg. dev. */
|
|
/* Note dev/sep is 0-1.0 at this stage */
|
|
for (j = 0; j < inn; j++) {
|
|
double dv = sep[j];
|
|
if (rdlevel > 0.0) {
|
|
double rr;
|
|
if (unidist)
|
|
rr = d_rand(-2.0 * rdlevel, 2.0 * rdlevel);
|
|
else
|
|
rr = 1.2533 * rdlevel * norm_rand();
|
|
dv += rr;
|
|
if (dv < 0.0)
|
|
dv = 0.0;
|
|
else if (dv > 1.0)
|
|
dv = 1.0;
|
|
}
|
|
if (j < 10 && chpow[j] != 1.0) {
|
|
dv = pow(dv, chpow[j]);
|
|
}
|
|
sep[j] = dv;
|
|
}
|
|
|
|
/* Do color conversion */
|
|
if (icc_luo != NULL) {
|
|
if (bt1886) {
|
|
icmLuMatrix *lu = (icmLuMatrix *)icc_luo; /* Safe to coerce */
|
|
//printf("Matrix dev in: %s\n",icmPdv(inn, sep));
|
|
bt1886_fwd_curve(&bt, sep, sep);
|
|
//printf("Matrix after bt1886 curve: %s\n",icmPdv(inn, sep));
|
|
lu->core5_fwd(lu, PCS, sep);
|
|
//printf("Matrix after matrix XYZ %s Lab %s\n",icmPdv(3, PCS), icmPLab(PCS));
|
|
bt1886_wp_adjust(&bt, PCS, PCS);
|
|
//printf("Matrix after bt1186 wp adj. XYZ %s Lab %s\n",icmPdv(3, PCS), icmPLab(PCS));
|
|
lu->output_pch_fwd(lu, PCS, PCS);
|
|
lu->output_fmt_fwd(lu, PCS, PCS);
|
|
//printf("Matrix after abs %s\n",icmPdv(3, PCS));
|
|
} else {
|
|
if (icc_luo->lookup_fwd(icc_luo, PCS, sep) & icmPe_lurv_err)
|
|
error ("%d, %s",icc_icco->e.c,icc_icco->e.m);
|
|
}
|
|
|
|
if (tbp[0] >= 0) { /* Doing black point scaling */
|
|
|
|
for (j = 0; j < 3; j++)
|
|
PCS[j] -= wp[j];
|
|
icmMulBy3x3(PCS, bpt, PCS);
|
|
for (j = 0; j < 3; j++)
|
|
PCS[j] += wp[j];
|
|
}
|
|
|
|
} else if (mlu != NULL) {
|
|
mlu->lookup(mlu, PCS, sep);
|
|
if (dospec && spec_n > 0) {
|
|
mlu->lookup_spec(mlu, &out, sep);
|
|
}
|
|
} else if (ti3 != NULL) {
|
|
int m;
|
|
double bdif = 1e6;
|
|
int bix = -1;
|
|
|
|
/* Search for the closest device values in TI3 file */
|
|
for (m = 0; m < ti3_npat; m++) {
|
|
double dif;
|
|
|
|
for (dif = 0.0, j = 0; j < nchan; j++) {
|
|
double xx;
|
|
|
|
xx = (*((double *)ti3->t[0].fdata[m][ti3_chix[j]]) / 100.0) - sep[j];
|
|
dif += xx * xx;
|
|
}
|
|
if (dif < bdif) {
|
|
bdif = dif;
|
|
bix = m;
|
|
}
|
|
}
|
|
/* Copy best value over */
|
|
if (!dosep) /* Doesn't make sense for separation ??? */
|
|
for (j = 0; j < nchan; j++) {
|
|
dev[j] = *((double *)ti3->t[0].fdata[bix][ti3_chix[j]]) / 100.0;
|
|
}
|
|
for (j = 0; j < 3; j++) {
|
|
PCS[j] = *((double *)ti3->t[0].fdata[bix][ti3_pcsix[j]]);
|
|
}
|
|
if (ti3_isLab && !dolab) { /* Convert Lab to XYZ */
|
|
icmLab2XYZ(&icmD50, PCS, PCS);
|
|
} else if (!ti3_isLab && dolab) { /* Convert XYZ to Lab */
|
|
icmXYZ2Lab(&icmD50, PCS, PCS);
|
|
} else if (!ti3_isLab) { /* Convert XYZ100 to XYZ1 */
|
|
PCS[0] /= 100.0;
|
|
PCS[1] /= 100.0;
|
|
PCS[2] /= 100.0;
|
|
}
|
|
if (dospec && spec_n > 0) {
|
|
for (j = 0; j < spec_n; j++) {
|
|
out.spec[j] = *((double *)ti3->t[0].fdata[bix][ti3_spi[j]]);
|
|
}
|
|
}
|
|
}
|
|
|
|
id = ((char *)icg->t[0].fdata[i][si]);
|
|
setel[k++].c = id;
|
|
|
|
for (j = 0; j < nchan; j++)
|
|
setel[k++].d = 100.0 * odev[j];
|
|
|
|
if (dolab == 0) {
|
|
PCS[0] *= 100.0;
|
|
PCS[1] *= 100.0;
|
|
PCS[2] *= 100.0;
|
|
}
|
|
|
|
/* Add randomness. rplevel is avg. dev. */
|
|
/* Note PCS is 0..100 XYZ or Lab at this point */
|
|
/* Adding uniform error to XYZ produces unreasonable */
|
|
/* bumpiness near black, so scale it by Y */
|
|
if (rplevel > 0.0) {
|
|
double opcs[3], ll = 1.0;
|
|
if (!dolab)
|
|
ll = 0.01 * PCS[1];
|
|
for (j = 0; j < 3; j++) {
|
|
double dv = PCS[j];
|
|
double rr;
|
|
opcs[j] = dv;
|
|
if (unidist)
|
|
rr = 100.0 * d_rand(-2.0 * rplevel, 2.0 * rplevel);
|
|
else
|
|
rr = 100.0 * 1.2533 * rplevel * norm_rand();
|
|
dv += ll * rr;
|
|
|
|
/* Don't let L*, X, Y or Z go negative */
|
|
if ((!dolab || j == 0) && dv < 0.0)
|
|
dv = 0.0;
|
|
PCS[j] = dv;
|
|
}
|
|
//printf("~1 pcs %f %f %f -> %f %f %f\n", opcs[0], opcs[1], opcs[2], PCS[0], PCS[1], PCS[2]);
|
|
}
|
|
|
|
setel[k++].d = PCS[0];
|
|
setel[k++].d = PCS[1];
|
|
setel[k++].d = PCS[2];
|
|
|
|
if (dospec && spec_n > 0) {
|
|
for (j = 0; j < spec_n; j++) {
|
|
setel[k++].d = 100.0 * out.spec[j];
|
|
}
|
|
}
|
|
|
|
ocg->add_setarr(ocg, 0, setel);
|
|
}
|
|
|
|
/* Do reverse (PCS -> device) lookup */
|
|
} else {
|
|
|
|
/* Read all the PCS test patches in, convert them to device values, */
|
|
/* and write them out. */
|
|
for (i = 0; i < npat; i++) {
|
|
int k = 0;
|
|
char *id;
|
|
double pcs[3];
|
|
double dev[ICX_MXINKS];
|
|
|
|
/* read PCS values */
|
|
for (j = 0; j < 3; j++) {
|
|
double pv = *((double *)icg->t[0].fdata[i][pcsix[j]]);
|
|
if (!islab)
|
|
pv /= 100.0;
|
|
pcs[j] = pv;
|
|
}
|
|
|
|
/* Do PCS to device color conversion */
|
|
if (icc_luo->lookup_fwd(icc_luo, dev, pcs) & icmPe_lurv_err)
|
|
error ("%d, %s",icc_icco->e.c,icc_icco->e.m);
|
|
|
|
/* Write the values */
|
|
id = ((char *)icg->t[0].fdata[i][si]);
|
|
setel[k++].c = id;
|
|
|
|
for (j = 0; j < nchan; j++)
|
|
setel[k++].d = 100.0 * dev[j];
|
|
|
|
for (j = 0; j < 3; j++) {
|
|
double pv = pcs[j];
|
|
if (!islab)
|
|
pv *= 100.0;
|
|
setel[k++].d = pv;
|
|
}
|
|
|
|
ocg->add_setarr(ocg, 0, setel);
|
|
}
|
|
}
|
|
|
|
free(setel);
|
|
free(ident);
|
|
free(bident);
|
|
}
|
|
|
|
if (sep_luo != NULL) {
|
|
sep_luo->del(sep_luo);
|
|
sep_icco->del(sep_icco);
|
|
sep_fp->del(sep_fp);
|
|
}
|
|
|
|
/* If there is a calibration, append it to the .ti3 file */
|
|
if (cal != NULL) {
|
|
if (cal->write_cgats(cal, ocg) != 0)
|
|
error("Writing cal error : %s",cal->e.m);
|
|
}
|
|
|
|
if (cal != NULL)
|
|
cal->del(cal);
|
|
|
|
|
|
if (icc_luo != NULL) {
|
|
/* Cleanup ICC profile */
|
|
icc_luo->del(icc_luo);
|
|
icc_icco->del(icc_icco);
|
|
icc_fp->del(icc_fp);
|
|
} else if (mlu != NULL) {
|
|
mlu->del(mlu);
|
|
} else if (ti3 != NULL) {
|
|
ti3->del(ti3);
|
|
}
|
|
|
|
if (ocg->write_name(ocg, outname))
|
|
error("Write error : %s",ocg->e.m);
|
|
|
|
ocg->del(ocg); /* Clean up */
|
|
icg->del(icg); /* Clean up */
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
|