Files
argyllcms/profile/invprofcheck.c
gronod d0209ec246
Linux x86_64 Release Build / build (push) Failing after 1m5s
Windows x86_64 Release Build / build (push) Has been cancelled
macOS Release Build / build (arm64, macOS_arm64_bin, arm64-apple-darwin, macos-latest) (push) Has been cancelled
macOS Release Build / build (x86_64, osx10.6_x86_64_bin, x86_64-apple-darwin, macos-13) (push) Has been cancelled
Fix macOS UI link errors by adding missing ui.h includes
2026-08-22 21:27:56 +01:00

618 lines
16 KiB
C

/*
* Argyll Color Management System
* Inverse profile checker.
*
* Author: Graeme W. Gill
* Date: 1999/11/29
*
* Copyright 1999 - 2005 Graeme W. Gill
* All rights reserved.
*
* This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
* see the License.txt file for licencing details.
*/
/*
* This program takes checks the round trip errors of
* the colorimetric forward and inverse profile direction
* of an ICC profile.
* (Was called icc/fbtest.c)
*/
/* TTBD:
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <fcntl.h>
#include <string.h>
#include <math.h>
#include <ctype.h>
#include "copyright.h"
#include "aconfig.h"
#include "numlib.h"
#include "icc.h"
#include "conv.h"
#include "xicc.h"
#include "vrml.h"
/* Resolution of the sampling modes */
#define TRES 11
#define HTRES 27
#define UHTRES 61
/* ------------------------------------------------------- */
/* Macros for an di or fdi dimensional counter */
/* Declare the counter name nn, dimensions di, & count */
#define DCOUNT(nn, di, start, reset, count) \
int nn[MAX_CHAN]; /* counter value */ \
int nn##_di = (di); /* Number of dimensions */ \
int nn##_stt = (start); /* start count value */ \
int nn##_rst = (reset); /* reset on carry value */ \
int nn##_res = (count); /* last count +1 */ \
int nn##_e /* dimension index */
/* Set the counter value to 0 */
#define DC_INIT(nn) \
{ \
for (nn##_e = 0; nn##_e < nn##_di; nn##_e++) \
nn[nn##_e] = nn##_stt; \
nn##_e = 0; \
}
/* Increment the counter value */
#define DC_INC(nn) \
{ \
for (nn##_e = 0; nn##_e < nn##_di; nn##_e++) { \
nn[nn##_e]++; \
if (nn[nn##_e] < nn##_res) \
break; /* No carry */ \
nn[nn##_e] = nn##_rst; \
} \
}
/* After increment, expression is TRUE if counter is done */
#define DC_DONE(nn) \
(nn##_e >= nn##_di)
/* ---------------------------------------- */
void usage(void) {
fprintf(stderr,"Check fwd to bwd relative transfer of an ICC file, Version %s\n",ARGYLL_VERSION_STR);
fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
fprintf(stderr,"usage: invprofcheck [-] profile.icm\n");
fprintf(stderr," -v [level] Verbosity level (default 1), 2 to print each DE\n");
fprintf(stderr," -l limit Set total ink limit (estimate by default)\n");
fprintf(stderr," -L klimit Set black channel ink limit (estimate by default)\n");
fprintf(stderr," -i intent a = absolute, r = relative colorimetric (def.)\n");
fprintf(stderr," p = perceptual, s = saturation\n");
fprintf(stderr," -h High res test (%d)\n",HTRES);
fprintf(stderr," -u Ultra high res test (%d)\n",UHTRES);
fprintf(stderr," -R res Specific grid resolution\n");
fprintf(stderr," -I Do bwd to fwd check\n");
fprintf(stderr," -c Show CIE94 delta E values\n");
fprintf(stderr," -k Show CIEDE2000 delta E values\n");
fprintf(stderr," -w Create %s visualisation (profile%s)\n",vrml_format(),vrml_ext());
fprintf(stderr," -x Use %s axes\n",vrml_format());
fprintf(stderr," -e Color vectors according to delta E\n");
fprintf(stderr," -Y c:file.inkvol Provide or overide calibration total ink curves\n");
fprintf(stderr," profile.icm Profile to check\n");
exit(1);
}
static void DE2RGB(double *out, double in);
#include "ui.h"
int
main(
int argc,
char *argv[]
) {
int fa,nfa; /* argument we're looking at */
int verb = 0;
int cie94 = 0;
int cie2k = 0;
int dovrml = 0;
int doaxes = 0;
int dodecol = 0;
static char in_name[MAXNAMEL+1];
static char out_name[MAXNAMEL+1], *xl; /* VRML/X3D name */
icmFile *rd_fp;
icmErr err = { 0, { '\000'} };
icc *icco;
int rv = 0;
int inv = 0;
int tres = TRES;
double tlimit = -1.0;
double klimit = -1.0;
static char tcalname[MAXNAMEL+1] = ""; /* .cal overide for icc */
icRenderingIntent intent = icRelativeColorimetric; /* Default */
vrml *wrl = NULL;
error_program = "invprofcheck";
if (argc < 2)
usage();
/* Process the 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) < argc) {
if (argv[fa+1][0] != '-') {
nfa = fa + 1;
na = argv[nfa]; /* next is seperate non-flag argument */
}
}
}
if (argv[fa][1] == '?')
usage();
/* Verbosity */
else if (argv[fa][1] == 'v' || argv[fa][1] == 'V') {
verb = 1;
if (na != NULL && isdigit(na[0])) {
verb = atoi(na);
}
}
/* Resolution */
else if (argv[fa][1] == 'h' || argv[fa][1] == 'H') {
tres = HTRES;
}
/* Resolution */
else if (argv[fa][1] == 'u' || argv[fa][1] == 'U') {
tres = UHTRES;
}
/* Resolution */
else if (argv[fa][1] == 'R') {
int res;
if (na == NULL) usage();
fa = nfa;
res = atoi(na);
if (res < 2 || res > 500)
usage();
tres = res;
}
/* Inverse */
else if (argv[fa][1] == 'I') {
inv = 1;
}
else if (argv[fa][1] == 'l') {
int limit;
if (na == NULL) usage();
fa = nfa;
limit = atoi(na);
if (limit < 1)
limit = 1;
tlimit = limit/100.0;
}
else if (argv[fa][1] == 'L') {
int limit;
if (na == NULL) usage();
fa = nfa;
limit = atoi(na);
if (limit < 1)
limit = 1;
klimit = limit/100.0;
}
/* Intent */
else if (argv[fa][1] == 'i') {
if (na == NULL) usage();
fa = nfa;
switch (na[0]) {
case 'p':
intent = icPerceptual;
break;
case 'r':
intent = icRelativeColorimetric;
break;
case 's':
intent = icSaturation;
break;
case 'a':
intent = icAbsoluteColorimetric;
break;
default:
usage();
}
}
/* VRML/X3D */
else if (argv[fa][1] == 'w' || argv[fa][1] == 'W')
dovrml = 1;
/* Axes */
else if (argv[fa][1] == 'x' || argv[fa][1] == 'X')
doaxes = 1;
/* Delta E coloring */
else if (argv[fa][1] == 'e' || argv[fa][1] == 'E')
dodecol = 1;
else if (argv[fa][1] == 'c' || argv[fa][1] == 'C') {
cie94 = 1;
cie2k = 0;
}
else if (argv[fa][1] == 'k' || argv[fa][1] == 'K') {
cie94 = 0;
cie2k = 1;
}
/* Extra flags */
else if (argv[fa][1] == 'Y') {
fa = nfa;
if (na == NULL)
usage();
if (na[0] == 'c') {
if (na[1] != ':')
usage();
strncpy(tcalname,&na[2],MAXNAMEL); tcalname[MAXNAMEL] = '\000';
} else
{
usage();
}
}
else
usage();
}
else
break;
}
if (fa >= argc || argv[fa][0] == '-') usage();
strncpy(in_name,argv[fa++],MAXNAMEL); in_name[MAXNAMEL] = '\000';
strncpy(out_name,in_name,MAXNAMEL-4); out_name[MAXNAMEL-4] = '\000';
if ((xl = strrchr(out_name, '.')) == NULL) /* Figure where extention is */
xl = out_name + strlen(out_name);
xl[0] = '\000'; /* Remove extension */
/* Open up the file for reading */
if ((rd_fp = new_icmFileStd_name(&err,in_name,"r")) == NULL)
error ("Read: Can't open file '%s' (0x%x, '%s')",in_name,err.c,err.m);
if ((icco = new_icc(&err)) == NULL)
error ("Read: Creation of ICC object failed (0x%x, '%s')",err.c,err.m);
/* Read the header and tag list */
if ((rv = icco->read(icco,rd_fp,0)) != 0)
error ("Read: %d, %s",rv,icco->e.m);
/* Check the bwd lookup against the fwd function */
{
xcal *cal = NULL; /* Device calibration curves */
icColorSpaceSignature ins, outs; /* Type of input and output spaces of fwd */
int inn, outn; /* Channels of fwd conversion */
int kch; /* Black channel, -1 if not known/applicable */
icmLuSpace *luo1, *luo2;
double merr = 0.0; /* Max */
double aerr = 0.0; /* Avg */
double rerr = 0.0; /* RMS */
double nsamps = 0.0;
/* Read any user supplied cal curves */
if (tcalname[0] != '\000') {
xcal *xc;
if ((xc = new_xcal()) == NULL)
error("new_xcal failed");
if ((xc->read(xc, tcalname)) != 0)
error("%s",xc->e.m);
cal = xc;
}
/* Get a Device to PCS conversion object */
if ((luo1 = (icmLuSpace *)icco->get_luobj(icco, icmFwd, intent, icSigLabData, icmLuOrdNorm)) == NULL) {
if ((luo1 = (icmLuSpace *)icco->get_luobj(icco, icmFwd, icmDefaultIntent, icSigLabData, icmLuOrdNorm)) == NULL)
error ("%d, %s",icco->e.c, icco->e.m);
}
/* Get details of conversion */
{
icmCSInfo ini, outi;
luo1->spaces(luo1, &ini, &outi, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
ins = ini.sig;
inn = ini.nch;
outs = outi.sig;
outn = outi.nch;
}
/* Get a PCS to Device conversion object */
if ((luo2 = (icmLuSpace *)icco->get_luobj(icco, icmBwd, intent, icSigLabData, icmLuOrdNorm)) == NULL) {
if ((luo2 = (icmLuSpace *)icco->get_luobj(icco, icmBwd, icmDefaultIntent, icSigLabData, icmLuOrdNorm)) == NULL)
error ("%d, %s",icco->e.c, icco->e.m);
}
if (dovrml) {
wrl = new_vrml(out_name, doaxes, vrml_lab);
wrl->start_line_set(wrl, 0);
}
/* Grab any device calibration curves if the user hasn't overriden it */
if (cal == NULL)
cal = xiccReadCalTag(icco);
kch = icxGuessBlackChan(icco);
/* Set the default ink limits if not set by user */
if (tlimit < 0.0 || klimit < 0.0) {
double max[MAX_CHAN], total;
icxCalCBctx ctx;
ctx.cal = cal;
if (cal != NULL
) {
total = icco->get_tac(icco, max, xiccCalCallback_void, (void *)&ctx);
if (total > 0.0) {
{
if (verb) printf("B2A Final TAC = %f, chan max = %s\n",total,
icmPdv(icmCSSig2nchan(ins), max));
}
}
} else {
total = icco->get_tac(icco, max, NULL, NULL);
}
if (tlimit < 0.0)
tlimit = total;
if (klimit < 0.0 && kch >= 0)
klimit = max[kch];
}
if (verb) {
printf("Grid resolution is %d\n",tres);
if (tlimit >= 0.0)
printf("Input total ink limit assumed is %3.1f%%\n",100.0 * tlimit);
if (klimit >= 0.0)
printf("Input black ink limit assumed is %3.1f%%\n",100.0 * klimit);
}
/* Device -> PCS -> Device */
if (inv == 0) {
double dev[MAX_CHAN], cdev[MAX_CHAN], clipdev[MAX_CHAN], pcsin[3], devout[MAX_CHAN], pcsout[3];
DCOUNT(co, inn, 0, 0, tres); /* Multi-D counter */
/* Go through the chosen device grid */
DC_INIT(co)
for (; !DC_DONE(co);) {
int n, rv1, rv2;
double sum;
double de;
/* Check the (possibly calibrated) device values */
/* and reject any more than 10% over the limits. */
for (n = 0; n < inn; n++)
cdev[n] = dev[n] = co[n]/(tres-1.0);
if (cal != NULL)
cal->interp(cal, cdev, cdev);
for (sum = 0.0, n = 0; n < inn; n++)
sum += cdev[n];
if ((tlimit > 0.0 && sum > (1.1 * tlimit))
|| (klimit > 0.0 && kch >= 0 && cdev[kch] > (1.1 * klimit))) {
DC_INC(co);
continue;
}
/* Generate the raw PCS test point value */
/* by converting device to pcsin */
if ((rv1 = luo1->lookup_fwd(luo1, pcsin, dev)) & icmPe_lurv_err)
error ("%d, %s",icco->e.c,icco->e.m);
/* Convert to gamut clipped device values */
if ((rv2 = luo2->lookup_fwd(luo2, clipdev, pcsin)) & icmPe_lurv_err)
error ("%d, %s",icco->e.c,icco->e.m);
/* clipped Device to target PCS */
if ((rv2 = luo1->lookup_fwd(luo1, pcsin, clipdev)) & icmPe_lurv_err)
error ("%d, %s",icco->e.c,icco->e.m);
/* Now do the round trip check */
/* PCS -> Device */
if ((rv2 = luo2->lookup_fwd(luo2, devout, pcsin)) & icmPe_lurv_err)
error ("%d, %s",icco->e.c,icco->e.m);
/* Device to PCS */
if ((rv2 = luo1->lookup_fwd(luo1, pcsout, devout)) & icmPe_lurv_err)
error ("%d, %s",icco->e.c,icco->e.m);
/* Delta E */
if (dovrml) {
int ix[2];
/* Add the vertices */
ix[0] = wrl->add_vertex(wrl, 0, pcsin);
ix[1] = wrl->add_vertex(wrl, 0, pcsout);
/* Add the line */
if (dodecol) { /* Lines with color determined by length */
double rgb[3];
DE2RGB(rgb, icmNorm33(pcsin, pcsout));
wrl->add_col_line(wrl, 0, ix, rgb);
} else { /* Natural color */
wrl->add_line(wrl, 0, ix);
}
}
/* Check the result */
if (cie2k)
de = icmCIE2K(pcsout, pcsin);
else if (cie94)
de = icmCIE94(pcsout, pcsin);
else
de = icmLabDE(pcsout, pcsin);
aerr += de;
rerr += de * de;
if (de > merr)
merr = de;
nsamps++;
if (verb > 1) {
printf("[%f] %f %f %f -> ",de, pcsin[0], pcsin[1], pcsin[2]);
for (n = 0; n < inn; n++)
printf("%f ",devout[n]);
printf("-> %f %f %f\n",pcsout[0], pcsout[1], pcsout[2]);
}
DC_INC(co);
}
/* PCS -> Device -> PCS */
/* Note that we're not rejecting out of gamut values ?? */
} else {
double dev[MAX_CHAN], cdev[MAX_CHAN], pcsin[3], devout[MAX_CHAN], pcsout[3];
DCOUNT(co, 3, 0, 0, tres); /* Multi-D counter */
/* Go through the chosen Lab grid */
DC_INIT(co)
for (; !DC_DONE(co);) {
int n, rv1, rv2;
double sum;
double de;
pcsin[0] = 100.0 * co[0]/(tres-1.0);
pcsin[1] = (127.0 * 2.0 * co[1]/(tres-1.0)) - 127.0;
pcsin[2] = (127.0 * 2.0 * co[2]/(tres-1.0)) - 127.0;
/* PCS -> Device (will clip) */
if ((rv2 = luo2->lookup_fwd(luo2, devout, pcsin)) & icmPe_lurv_err)
error ("%d, %s",icco->e.c,icco->e.m);
/* Device to PCS */
if ((rv2 = luo1->lookup_fwd(luo1, pcsout, devout)) & icmPe_lurv_err)
error ("%d, %s",icco->e.c,icco->e.m);
/* Delta E */
if (dovrml) {
// if (fabs(pcsin[0] - 5.0) < 0.1 && dovrml) {
int ix[2];
/* Add the vertices */
ix[0] = wrl->add_vertex(wrl, 0, pcsin);
ix[1] = wrl->add_vertex(wrl, 0, pcsout);
/* Add the line */
if (dodecol) { /* Lines with color determined by length */
double rgb[3];
DE2RGB(rgb, icmNorm33(pcsin, pcsout));
wrl->add_col_line(wrl, 0, ix, rgb);
} else { /* Natural color */
wrl->add_line(wrl, 0, ix);
}
}
/* Check the result */
if (cie2k)
de = icmCIE2K(pcsout, pcsin);
else if (cie94)
de = icmCIE94(pcsout, pcsin);
else
de = icmLabDE(pcsout, pcsin);
aerr += de;
rerr += de * de;
if (de > merr)
merr = de;
nsamps++;
if (verb > 1) {
printf("[%f] %f %f %f -> ",de, pcsin[0], pcsin[1], pcsin[2]);
for (n = 0; n < inn; n++)
printf("%f ",devout[n]);
printf("-> %f %f %f\n",pcsout[0], pcsout[1], pcsout[2]);
}
DC_INC(co);
}
}
if (dovrml) {
wrl->make_lines_vc(wrl, 0, 0.0);
wrl->del(wrl);
}
printf("Profile check complete, errors%s: max. = %f, avg. = %f, RMS = %f\n",
cie2k ? "(CIEDE2000)" : cie94 ? " (CIE94)" : "", merr, aerr/nsamps, sqrt(rerr/nsamps));
/* Done with lookup object */
luo1->del(luo1);
luo2->del(luo2);
}
icco->del(icco);
rd_fp->del(rd_fp);
return 0;
}
/* ------------------------------------------------ */
/* Convert a delta E value into a signal color: */
static void DE2RGB(double *out, double in) {
struct {
double de;
double r, g, b;
} range[6] = {
{ 10.0, 1, 1, 0 }, /* yellow */
{ 4.0, 1, 0, 0 }, /* red */
{ 2.0, 1, 0, 1 }, /* magenta */
{ 1.0, 0, 0, 1 }, /* blue */
{ 0.5, 0, 1, 1 }, /* cyan */
{ 0.0, 0, 1, 0 } /* green */
};
int i;
double bl;
//printf("~1 input de = %f\n",in);
/* Locate the range we're in */
if (in > range[0].de) {
out[0] = range[0].r;
out[1] = range[0].g;
out[2] = range[0].b;
//printf("~1 too big\n");
} else {
for (i = 0; i < 5; i++) {
if (in <= range[i].de && in >= range[i+1].de)
break;
}
bl = (in - range[i+1].de)/(range[i].de - range[i+1].de);
//printf("~1 located at ix %d, bl = %f\n",i,bl);
out[0] = bl * range[i].r + (1.0 - bl) * range[i+1].r;
out[1] = bl * range[i].g + (1.0 - bl) * range[i+1].g;
out[2] = bl * range[i].b + (1.0 - bl) * range[i+1].b;
}
//printf("~1 returning rgb %f %f %f\n",out[0],out[1],out[2]);
}