Files
argyllcms/icc/icclu.c
2026-08-20 20:28:40 +01:00

533 lines
13 KiB
C

/*
* International Color Consortium Format Library (icclib)
* Profile color lookup utility.
*
* Author: Graeme W. Gill
* Date: 1999/11/29
* Version: 2.15
*
* Copyright 1998 - 2012 Graeme W. Gill
*
* This material is licensed with an "MIT" free use license:-
* see the License4.txt file in this directory for licensing details.
*/
/* TTBD:
*
*/
/*
This file is intended to exercise the ability
of the icc library to translate colors through a profile.
It also serves as a concise example of how to do this.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <fcntl.h>
#include <string.h>
#include <math.h>
#include "icc.h"
void error(char *fmt, ...), warning(char *fmt, ...);
void usage(void) {
fprintf(stderr,"Translate colors through an ICC profile, V%s\n",ICCLIB_VERSION_STR);
fprintf(stderr,"Author: Graeme W. Gill\n");
fprintf(stderr,"usage: icclu [-v level] [-f func] [-i intent] [-o order] profile\n");
fprintf(stderr," -v level Verbosity level 0 - 2 (default = 1)\n");
fprintf(stderr," -f function f = forward, b = backwards, g = gamut, p = preview\n");
fprintf(stderr," -i intent p = perceptual, r = relative colorimetric,\n");
fprintf(stderr," s = saturation, a = absolute\n");
// fprintf(stderr," P = absolute perceptual, S = absolute saturation\n");
fprintf(stderr," -p oride x = XYZ_PCS, l = Lab_PCS, y = Yxy,\n");
fprintf(stderr," -o order n = normal (priority: lut > matrix > monochrome)\n");
fprintf(stderr," r = reverse (priority: monochrome > matrix > lut)\n");
fprintf(stderr," -s scale Scale device range 0.0 - scale rather than 0.0 - 1.0\n");
fprintf(stderr," -D verb Dump the Pe's of all conversions\n");
fprintf(stderr," -T Trace each step of conversions\n");
fprintf(stderr,"\n");
fprintf(stderr," The colors to be translated should be fed into standard input,\n");
fprintf(stderr," one input color per line, white space separated.\n");
fprintf(stderr," A line starting with a # will be ignored.\n");
fprintf(stderr," A line not starting with a number will terminate the program.\n");
exit(1);
}
int
main(int argc, char *argv[]) {
int fa,nfa; /* argument we're looking at */
icmErr e = { 0, { '\000'} };
static char prof_name[MAXNAMEL+1];
icmFile *fp;
icc *icco;
int verb = 1;
double scale = 0.0; /* Device value scale factor */
int rv = 0;
int repYxy = 0; /* Report Yxy */
char buf[200];
double oin[MAX_CHAN], in[MAX_CHAN], out[MAX_CHAN];
int dump = 0; /* Dump the conversions */
int trace = 0; /* Trace the conversions */
icmLuSpace *luo;
icmCSInfo ini, outi, pcsi; /* Type of input and output spaces */
icmCSInfo n_ini, n_outi, n_pcsi; /* Native type of input and output spaces */
icTagSignature srctag; /* Source tag of lookup */
icRenderingIntent rintent; /* Rendering intent */
icmLookupFunc lufunc; /* Lookup function */
icmLookupOrder luord; /* Lookup order */
icmPeOp luop; /* Dominant processing element operation */
int can_bwd; /* Whether lookup is invertable */
#define ins ini.sig /* Compatibility until code is switched over */
#define outs outi.sig
#define inn ini.nch
#define outn outi.nch
/* Lookup parameters */
icmLookupFunc func = icmFwd; /* Default */
icRenderingIntent intent = icmDefaultIntent; /* Default */
icColorSpaceSignature pcsor = icmSigDefaultData; /* Default */
icmLookupOrder order = icmLuOrdNorm; /* Default */
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') {
fa = nfa;
if (na == NULL)
verb = 2;
else {
if (na[0] == '0')
verb = 0;
else if (na[0] == '1')
verb = 1;
else if (na[0] == '2')
verb = 2;
else if (na[0] == '3')
verb = 3;
else
usage();
}
}
/* function */
else if (argv[fa][1] == 'f') {
fa = nfa;
if (na == NULL) usage();
switch (na[0]) {
case 'f':
case 'F':
func = icmFwd;
break;
case 'b':
case 'B':
func = icmBwd;
break;
case 'g':
case 'G':
func = icmGamut;
break;
case 'p':
case 'P':
func = icmPreview;
break;
default:
usage();
}
}
/* Intent */
else if (argv[fa][1] == 'i') {
fa = nfa;
if (na == NULL) usage();
switch (na[0]) {
case 'p':
intent = icPerceptual;
break;
case 'r':
intent = icRelativeColorimetric;
break;
case 's':
intent = icSaturation;
break;
case 'a':
intent = icAbsoluteColorimetric;
break;
/* Special function icclib intents */
case 'P':
intent = icmAbsolutePerceptual;
break;
case 'S':
intent = icmAbsoluteSaturation;
break;
default:
usage();
}
}
/* Search order */
else if (argv[fa][1] == 'o') {
fa = nfa;
if (na == NULL) usage();
switch (na[0]) {
case 'n':
order = icmLuOrdNorm;
break;
case 'r':
order = icmLuOrdRev;
break;
default:
usage();
}
}
/* PCS override */
else if (argv[fa][1] == 'p') {
fa = nfa;
if (na == NULL) usage();
switch (na[0]) {
case 'x':
case 'X':
pcsor = icSigXYZData;
repYxy = 0;
break;
case 'l':
case 'L':
pcsor = icSigLabData;
repYxy = 0;
break;
case 'y':
case 'Y':
pcsor = icSigXYZData;
repYxy = 1;
break;
default:
usage();
}
}
/* Device scale */
else if (argv[fa][1] == 's') {
fa = nfa;
if (na == NULL) usage();
scale = atof(na);
if (scale <= 0.0) usage();
}
else if (argv[fa][1] == 'D') {
fa = nfa;
if (na == NULL) usage();
dump = atoi(na);
if (dump <= 0.0) usage();
}
else if (argv[fa][1] == 'T') {
trace = 1;
}
else
usage();
} else
break;
}
if (fa >= argc || argv[fa][0] == '-') usage();
strncpy(prof_name,argv[fa++],MAXNAMEL); prof_name[MAXNAMEL] = '\000';
icm_err_clear_e(&e);
/* Open up the profile for reading */
if ((fp = new_icmFileStd_name(&e, prof_name,"r")) == NULL)
error ("Can't open file '%s', failed with 0x%x, '%s'",prof_name, e.c, e.m);
if ((icco = new_icc(&e)) == NULL)
error ("Creation of ICC object failed with 0x%x, '%s'", e.c, e.m);
if ((rv = icco->read(icco,fp,0)) != 0)
error ("%d, %s",rv,icco->e.m);
if (icco->header->cmmId == icmstr2tag("argl"))
icco->allowclutPoints256 = 1;
/* Dump profile header if verbose */
if (verb > 1) {
icmFile *op;
if ((op = new_icmFileStd_fp(&e, stdout)) == NULL)
error ("Can't open stdout, failed with 0x%x, '%s'", e.c, e.m);
icco->header->dump(icco->header, op, 1);
op->del(op);
}
/* Compute a TAC */
if (verb > 1) {
double tac;
double chmax[MAX_CHAN];
tac = icco->get_tac(icco, chmax, NULL, NULL);
if (tac <= -1.0)
printf("No TAC\n\n");
else
printf("TAC = %f, chan max = %s\n\n",tac,
icmPdv(icmCSSig2nchan(icco->header->colorSpace), chmax));
}
/* Get a conversion object */
if ((luo = (icmLuSpace *)icco->get_luobj(icco, func, intent, pcsor, order)) == NULL)
error ("%d, %s",icco->e.c, icco->e.m);
if (dump > 0) {
icmErr err = { 0 };
icmFile *op;
int verb = 1;
if ((op = new_icmFileStd_fp(&err, stdout)) == NULL) {
printf("Can't open stdout stream, failed with 0x%x, '%s'",err.c, err.m);
} else {
printf("\nOverall transform:\n");
luo->lookup->dump(luo->lookup, op, dump);
printf("\n3 Step transform:\n");
luo->input->dump(luo->input, op, dump);
luo->core3->dump(luo->core3, op, dump);
luo->output->dump(luo->output, op, dump);
printf("\n5 Step transform:\n");
luo->input_fmt->dump(luo->input_fmt, op, dump);
luo->input_pch->dump(luo->input_pch, op, dump);
luo->core5->dump(luo->core5, op, dump);
luo->output_pch->dump(luo->output_pch, op, dump);
luo->output_fmt->dump(luo->output_fmt, op, dump);
op->del(op);
}
}
if (trace) {
luo->lookup->trace = 1;
}
/* Get details of conversion (Arguments may be NULL if info not needed) */
luo->spaces(luo, &ini, &outi, &pcsi, &srctag, &rintent, &lufunc, &luord, &luop, &can_bwd);
luo->native_spaces(luo, &n_ini, &n_outi, &n_pcsi);
if (verb > 1) {
double pcs[3], white[3], black[3];
int asblk;
printf("Lookup details:\n");
printf(" ini = %s\n",icmCSInfo2str(&ini));
printf(" outi = %s\n",icmCSInfo2str(&outi));
printf(" pcsi = %s\n",icmCSInfo2str(&pcsi));
printf(" n_ini = %s\n",icmCSInfo2str(&n_ini));
printf(" n_outi = %s\n",icmCSInfo2str(&n_outi));
printf(" n_pcsi = %s\n",icmCSInfo2str(&n_pcsi));
printf(" src_tag = %s\n",icm2str(icmTagSig_alt, srctag));
printf(" intent = %s\n",icm2str(icmRenderingIntent, rintent));
printf(" lu func = %s\n",icm2str(icmTransformLookupFunc, lufunc));
printf(" lu order = %s\n",icm2str(icmTransformLookupOrder, luord));
printf(" lu attr.op = %s\n",icm2str(icmProcessingElementOp, luop));
printf(" can bwd ? = %s\n",can_bwd ? "yes" : "no");
asblk = luo->wh_bk_points(luo, pcs, white, black);
printf("Abs. XYZ PCS %s, white %s, black %s%s\n",
icmPdv(3, pcs), icmPdv(3, white), icmPdv(3, black), asblk ? " (assumed)" : "");
asblk = luo->lu_wh_bk_points(luo, pcs, white, black);
printf("Rel. XYZ PCS %s, white %s, black %s%s\n",
icmPdv(3, pcs), icmPdv(3, white), icmPdv(3, black), asblk ? " (assumed)" : "");
}
/* Show other derived attributes */
if (verb > 2) {
int maxres;
int chmax[MAX_CHAN];
maxres = luo->max_in_res(luo, chmax);
printf("Input curve maxres = %d, chan max = %s\n\n",maxres,
icmPiv(luo->lookup->inputChan, chmax));
maxres = luo->max_clut_res(luo, chmax);
printf("cLut maxres = %d, chan max = %s\n\n",maxres,
icmPiv(luo->lookup->inputChan, chmax));
maxres = luo->max_out_res(luo, chmax);
printf("Ouput curve maxres = %d, chan max = %s\n\n",maxres,
icmPiv(luo->lookup->outputChan, chmax));
}
if (repYxy) { /* report Yxy rather than XYZ */
if (ini.sig == icSigXYZData)
ini.sig = icSigYxyData;
if (outi.sig == icSigXYZData)
outi.sig = icSigYxyData;
}
/* Process colors to translate */
for (;;) {
int i,j;
char *bp, *nbp;
/* Init default input values */
for (i = 0; i < MAX_CHAN; i++) {
in[i] = oin[i] = 0.0;
}
/* Read in the next line */
if (fgets(buf, 200, stdin) == NULL)
break;
if (buf[0] == '#') {
if (verb > 0)
fprintf(stdout,"%s\n",buf);
continue;
}
/* For each input number */
for (nbp = buf, i = 0; i < MAX_CHAN; i++) {
bp = nbp;
in[i] = oin[i] = strtod(bp, &nbp);
if (nbp == bp)
break; /* Failed */
}
if (i == 0)
break;
/* If device data and scale */
if(scale > 0.0
&& ins != icSigXYZData
&& ins != icSigLabData
&& ins != icSigLuvData
&& ins != icSigYCbCrData
&& ins != icSigYxyData
&& ins != icSigHsvData
&& ins != icSigHlsData) {
for (i = 0; i < MAX_CHAN; i++) {
in[i] /= scale;
}
}
if (repYxy && ins == icSigYxyData) {
icmYxy2XYZ(in, in);
}
/* Do conversion */
if ((rv = luo->lookup_fwd(luo, out, in)) & icmPe_lurv_err)
error("%d, %s",icco->e.c,icco->e.m);
/* Output the results */
if (verb > 0) {
for (j = 0; j < inn; j++) {
if (j > 0)
fprintf(stdout," %f",oin[j]);
else
fprintf(stdout,"%f",oin[j]);
}
printf(" [%s] -> %s -> ", icm2str(icmColorSpaceSig, ins),
icm2str(icmTransformSourceTag, srctag));
}
if (repYxy && outs == icSigYxyData) {
icmXYZ2Yxy(out, out);
}
/* If device data and scale */
if(scale > 0.0
&& outs != icSigXYZData
&& outs != icSigLabData
&& outs != icSigLuvData
&& outs != icSigYCbCrData
&& outs != icSigYxyData
&& outs != icSigHsvData
&& outs != icSigHlsData) {
for (i = 0; i < MAX_CHAN; i++) {
out[i] *= scale;
}
}
for (j = 0; j < outn; j++) {
if (j > 0)
fprintf(stdout," %f",out[j]);
else
fprintf(stdout,"%f",out[j]);
}
if (verb > 0)
printf(" [%s]", icm2str(icmColorSpaceSig, outs));
if (rv != icmPe_lurv_OK)
printf(" (%s)",icmPe_lurv2str(rv));
fprintf(stdout,"\n");
}
/* Done with lookup object */
luo->del(luo);
icco->del(icco);
fp->del(fp);
return 0;
}
/* Basic printf type error() and warning() routines */
void
error(char *fmt, ...)
{
va_list args;
fprintf(stderr,"icclu: Error - ");
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
fprintf(stderr, "\n");
exit (-1);
}
void
warning(char *fmt, ...)
{
va_list args;
fprintf(stderr,"icclu: Warning - ");
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
fprintf(stderr, "\n");
}