Files
argyllcms/xicc/specplot.c
2026-08-20 20:28:40 +01:00

541 lines
13 KiB
C

/*
* International Color Consortium color transform expanded support
*
* Author: Graeme W. Gill
* Date: 2006/5/9
* Version: 1.00
*
* Copyright 2006 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 is some test code to test the Daylight and Plankian spectra,
* Correlated and Visual Color Temperatures, and CRI.
* and plot a spectrum, CMF or CCSS.
*/
#include <stdio.h>
#include <stdarg.h>
#include <math.h>
#include "aconfig.h"
#include "cgats.h"
#include "xspect.h"
#include "numlib.h"
#include "plot.h"
#include "ui.h"
#define PLANKIAN
#define XRES 500
#define MAXGRAPHS 10
/* Display a spectrum etc. */
/* We are guaranteed that the x range/increments are identical, */
/* and that there is only one spectrum if douv */
static int do_spec(
char name[MAXGRAPHS][200],
xspect *sp,
int nsp, /* Number of sp */
inst_meas_type mt, /* Measurement type */
int dozero, /* Include zero in the range */
int noplot, /* Don't actually plot */
icxDensityType dens, /* Density type to print */
int douv, /* Do variation of added UV test */
icxObserverType obType, /* Observer (defaults to 1931 2 degree) */
xspect *custObserver, /* If obType = icxOT_custom */
double uvmin,
double uvmax
) {
int n, i, j, k, m;
double wl_short, wl_long; /* Common range */
double xyz[3]; /* Color temperature */
double Yxy[3];
double Lab[3]; /* D50 Lab value */
double xx[XRES];
double yy[10][XRES];
double *yp[10];
double cct, vct;
double cct_xyz[3], vct_xyz[3];
double cct_lab[3], vct_lab[3];
double cdt, vdt;
double cdt_xyz[3], vdt_xyz[3];
double cdt_lab[3], vdt_lab[3];
icmXYZNumber wp;
double de;
double uv = uvmin;
double step = 0.1;
xspect tsp; /* Spectrum with possible UV added */
char *color[] = {
"Black", "Red", "Green", "Blue", "Yellow", "Purple", "Brown", "Orange", "Grey", "Magenta"
};
printf("\n");
for (j = 0; j < 10; j++)
yp[j] = NULL;
if (nsp > 10)
nsp = 10;
m = 0; /* offset in output array */
n = 1;
wl_short = 1e6;
wl_long = -1e6;
for (k = 0; k < nsp; k++) {
if (sp[k].spec_wl_long > wl_long)
wl_long = sp[k].spec_wl_long;
if (sp[k].spec_wl_short < wl_short)
wl_short = sp[k].spec_wl_short;
}
if (douv) {
n = 1 + (int)(0.5 + (uvmax-uvmin)/0.1);
if (n > 9)
n = 9; /* Don't use white */
if (n > 1)
step = (uvmax-uvmin)/(n-1.0);
}
for (k = 0; k < nsp; k++) {
tsp = sp[k];
for (uv = uvmax, j = 0; j < n; j++, uv -= step) {
if (douv) {
printf("UV level = %f\n",uv);
xsp_setUV(&tsp, &sp[k], uv);
}
printf("Type = %s [%s]\n",name[k], color[k]);
if (mt == inst_mrt_none
|| mt == inst_mrt_emission
|| mt == inst_mrt_ambient
|| mt == inst_mrt_emission_flash
|| mt == inst_mrt_ambient_flash) {
/* Compute absolute XYZ of illuminant */
if (icx_ill_sp2XYZ(xyz, obType, custObserver, icxIT_custom, 0, &tsp, 1) != 0)
warning("icx_ill_sp2XYZ returned error");
printf("Abs. Y = %f\n", xyz[1]);
/* Normalise to Y = 1 */
xyz[0] /= xyz[1];
xyz[2] /= xyz[1];
xyz[1] /= xyz[1];
icmXYZ2Yxy(Yxy, xyz);
icmXYZ2Lab(&icmD50, Lab, xyz);
printf("XYZ = %f %f %f, x,y = %f %f\n", xyz[0], xyz[1], xyz[2], Yxy[1], Yxy[2]);
printf("D50 L*a*b* = %f %f %f\n", Lab[0], Lab[1], Lab[2]);
/* Compute CCT */
if ((cct = icx_XYZ2ill_ct(cct_xyz, icxIT_Ptemp, obType, custObserver, xyz, NULL, 0)) < 0)
warning("Got bad cct\n");
/* Compute VCT */
if ((vct = icx_XYZ2ill_ct(vct_xyz, icxIT_Ptemp, obType, custObserver, xyz, NULL, 1)) < 0)
warning("Got bad vct\n");
printf("CCT = %f, VCT = %f\n",cct, vct);
/* Compute CDT */
if ((cct = icx_XYZ2ill_ct(cct_xyz, icxIT_Dtemp, obType, custObserver, xyz, NULL, 0)) < 0)
warning("Got bad cct\n");
/* Compute VDT */
if ((vct = icx_XYZ2ill_ct(vct_xyz, icxIT_Dtemp, obType, custObserver, xyz, NULL, 1)) < 0)
warning("Got bad vct\n");
printf("CDT = %f, VDT = %f\n",cct, vct);
{
int invalid = 0;
double RR[14];
double cri;
cri = icx_CIE1995_CRI(&invalid, RR, &tsp);
printf("CRI = %.1f [ R9 = %.1f ]%s\n",cri,RR[9-1],invalid ? " (Invalid)" : "");
}
{
int invalid = 0;
double tlci;
tlci = icx_EBU2012_TLCI(&invalid, &tsp);
printf("TLCI = %.1f%s\n",tlci,invalid ? " (Invalid)" : "");
}
/* Use modern color difference - gives a better visual match */
icmAry2XYZ(wp, vct_xyz);
icmXYZ2Lab(&wp, cct_lab, cct_xyz);
icmXYZ2Lab(&wp, vct_lab, vct_xyz);
de = icmCIE2K(cct_lab, vct_lab);
printf("CIEDE2000 Delta E = %f\n",de);
} else if (mt == inst_mrt_none
|| mt == inst_mrt_reflective
|| mt == inst_mrt_transmissive) {
double cmyv[4];
printf("CIE values under D50 illuminant:\n");
if (icx_sp2XYZ(xyz, obType, custObserver, icxIT_D50, 0, NULL, &tsp) != 0)
warning("icx_sp2XYZ returned error");
icmXYZ2Yxy(Yxy, xyz);
icmXYZ2Lab(&icmD50, Lab, xyz);
printf("XYZ = %f %f %f, x,y = %f %f\n", xyz[0], xyz[1], xyz[2], Yxy[1], Yxy[2]);
printf("D50 L*a*b* = %f %f %f\n", Lab[0], Lab[1], Lab[2]);
if (dens != icxDT_none) {
/* Density */
xsp_density(cmyv, &tsp, dens);
printf("CMYV %s = %f %f %f %f\n", xsp_density_desc(dens), cmyv[0], cmyv[1], cmyv[2], cmyv[3]);
}
} else {
printf("Unhandled measurement type '%s'\n",meas_type2str(mt));
}
/* Plot spectrum out */
for (i = 0; i < XRES; i++) {
double ww;
ww = (wl_long - wl_short)
* ((double)i/(XRES-1.0)) + wl_short;
xx[i] = ww;
yy[(m + k + j) % 10][i] = value_xspect(&tsp, ww);
}
yp[(m + k + j) % 10] = &yy[(m + k + j) % 10][0];
printf("\n");
}
}
if (!noplot) {
do_plot10(xx, yp[0], yp[1], yp[2], yp[3], yp[4], yp[5], yp[6], yp[7], yp[8], yp[9], XRES, dozero);
}
return 0;
}
void usage(void) {
fprintf(stderr,"Plot spectrum and calculate CCT and VCT\n");
fprintf(stderr,"Author: Graeme W. Gill, licensed under the AGPL Version 3\n");
fprintf(stderr,"usage: specplot [infile.sp]\n");
fprintf(stderr," -v verbose\n");
fprintf(stderr," -c combine multiple files into one plot\n");
fprintf(stderr," -n normalize max level\n");
fprintf(stderr," -z don't make range cover zero\n");
fprintf(stderr," -s don't plot spectra\n");
fprintf(stderr," -d I|A|M|T|E print density values\n");
fprintf(stderr," -u level plot effect of adding estimated UV level\n");
fprintf(stderr," -U plot effect of adding range of estimated UV level\n");
fprintf(stderr," -o observ Choose CIE Observer for spectral data:\n");
fprintf(stderr," 1931_2 (def), 1964_10, 2015_2, 2015_10, S&B 1955_2, shaw, J&V 1978_2, or file.cmf\n");
fprintf(stderr," [infile.sp ...] spectrum files to plot\n");
fprintf(stderr," default is all built in illuminants\n");
exit(1);
}
int main(int argc, char *argv[]) {
int fa, nfa; /* argument we're looking at */
int k;
int verb = 0;
int comb = 0;
int norm = 0;
int zero = 1;
int noplot = 0;
icxDensityType dens = icxDT_none;
double temp;
icxObserverType obType = icxOT_CIE_1931_2; /* Observer */
xspect custObserver[3]; /* If obType = icxOT_custom */
xspect sp[MAXGRAPHS];
icxIllumeType ilType;
int douv = 0;
double uvmin = -1.0, uvmax = 1.0;
char buf[MAXGRAPHS][200];
error_program = argv[0];
/* 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 */
}
}
}
/* Show added UV */
if (argv[fa][1] == 'u') {
douv = 1;
fa = nfa;
if (na == NULL)
usage();
uvmin = uvmax = atof(na);
if (uvmin < -10.0 || uvmax > 10.0)
usage();
}
else if (argv[fa][1] == 'U') {
douv = 1;
}
/* Spectral Observer type */
else if (argv[fa][1] == 'o') {
if (na == NULL) usage();
fa = nfa;
if (strcmp(na, "1931_2") == 0) { /* Classic 2 degree */
obType = icxOT_CIE_1931_2;
} else if (strcmp(na, "1964_10") == 0) { /* Classic 10 degree */
obType = icxOT_CIE_1964_10;
} else if (strcmp(na, "2015_2") == 0) { /* Latest 2 degree */
obType = icxOT_CIE_2015_2;
} else if (strcmp(na, "2015_10") == 0) { /* Latest 10 degree */
obType = icxOT_CIE_2015_10;
} else if (strcmp(na, "1955_2") == 0) { /* Stiles and Burch 1955 2 degree */
obType = icxOT_Stiles_Burch_2;
} else if (strcmp(na, "1978_2") == 0) { /* Judd and Voss 1978 2 degree */
obType = icxOT_Judd_Voss_2;
} else if (strcmp(na, "shaw") == 0) { /* Shaw and Fairchilds 1997 2 degree */
obType = icxOT_Shaw_Fairchild_2;
} else { /* Assume it's a filename */
obType = icxOT_custom;
if (read_cmf(custObserver, na) != 0) {
fprintf(stderr,"Failed to read custom observer CMF from -o file '%s'\n",na);
usage();
}
}
}
/* Verbosity */
else if (argv[fa][1] == 'v') {
verb = 1;
} else if (argv[fa][1] == 'c') {
comb = 1;
} else if (argv[fa][1] == 'n') {
norm = 1;
} else if (argv[fa][1] == 'z') {
zero = 0;
} else if (argv[fa][1] == 's') {
noplot = 1;
} else if (argv[fa][1] == 'd') {
if (na == NULL)
usage();
switch(na[0]) {
case 'N':
dens = icxDT_none;
break;
case 'I':
dens = icxDT_ISO;
break;
case 'A':
dens = icxDT_A;
break;
case 'M':
dens = icxDT_M;
break;
case 'T':
dens = icxDT_T;
break;
case 'E':
dens = icxDT_E;
break;
default:
usage();
}
fa = nfa;
} else {
usage();
}
}
else
break;
}
if (fa < argc && argv[fa][0] != '-') { /* Got file arguments */
int nsp = 0; /* Current number in sp[] */
int soff = 0; /* Offset within file */
int maxgraphs = MAXGRAPHS;
int eof;
if (douv)
maxgraphs = 1;
nsp = 0;
/* Until we run out */
for (;;) {
int i, nret, nreq;
inst_meas_type mt;
inst_meas_cond mc;
/* If we've got to the limit of each plot, */
/* or at least one and we're not combining files and at start of a new file, */
/* or at least one and there are no more files */
if (nsp >= MAXGRAPHS || (nsp > 0 && ((!comb && soff == 0) || fa >= argc))) {
if (norm) {
int j;
double mlev = 0.0;
for (i = 0; i < nsp; i++) {
xspect_denorm(&sp[i]);
for (j = 0; j < sp[i].spec_n; j++) {
if (sp[i].spec[j] > mlev)
mlev = sp[i].spec[j];
}
}
if (mlev > 1e-6) {
for (i = 0; i < nsp; i++) {
double mm = 0.0;
for (j = 0; j < sp[i].spec_n; j++) {
if (sp[i].spec[j] > mm)
mm = sp[i].spec[j];
}
if (mm > 1e-6) {
mm = mlev/mm;
for (j = 0; j < sp[i].spec_n; j++) {
sp[i].spec[j] *= mm;
}
}
}
}
}
/* Plot what we've got */
do_spec(buf, sp, nsp, mt, zero, noplot, dens, douv, obType, custObserver, uvmin, uvmax);
nsp = 0;
}
if (fa >= argc) /* No more files */
break;
/* Read as many spectra from the file as possible */
nreq = MAXGRAPHS - nsp;
if (read_nxspect(&sp[nsp], &mt, &mc, argv[fa], &nret, soff, nreq, 0) != 0) {
error ("Unable to read custom spectrum, CMF or CCSS '%s'",argv[fa]);
}
for (i = 0; i < nret; i++) {
xspect_denorm(&sp[nsp + i]);
sprintf(buf[nsp + i],"File '%s' spect %d",argv[fa], soff + i);
}
nsp += nret;
soff += nret;
if (nret < nreq) { /* We're done with this file */
fa++;
soff = 0;
}
}
} else {
/* For each standard illuminant */
for (ilType = icxIT_A; ilType <= icxIT_F10; ilType++) {
char *inm = NULL;
switch (ilType) {
case icxIT_A:
inm = "A"; break;
case icxIT_C:
inm = "C"; break;
case icxIT_D50:
inm = "D50"; break;
case icxIT_D50M2:
inm = "D50M2"; break;
case icxIT_D55:
inm = "D55"; break;
case icxIT_D65:
inm = "D65"; break;
case icxIT_D75:
inm = "D75"; break;
case icxIT_E:
inm = "E"; break;
case icxIT_F5:
inm = "F5"; break;
case icxIT_F8:
inm = "F8"; break;
case icxIT_F10:
inm = "F10"; break;
default:
inm = "Unknown"; break;
break;
}
if (standardIlluminant(&sp[0], ilType, 0) != 0)
error ("standardIlluminant returned error for %d (%s)",ilType,inm);
strcpy(buf[0],inm);
do_spec(buf, sp, 1, inst_mrt_ambient, zero, noplot, dens, douv, obType, custObserver, uvmin, uvmax);
}
/* For each material and illuminant */
for (k = 0; k < 2; k++) {
double start, end;
if (k == 0)
start = 3400, end = 9000;
else
start = 500, end = 9000;
for (temp = start; temp <= end; temp += 500) {
ilType = k == 0 ? icxIT_Dtemp : icxIT_Ptemp;
if (standardIlluminant(&sp[0], ilType, temp) != 0)
error ("standardIlluminant returned error for %s temp %f",
k == 0 ? "Daylight" : "Plankian", temp);
sprintf(buf[0], "%s at %f", k == 0 ? "Daylight" : "Black body", temp);
do_spec(buf, sp, 1, inst_mrt_ambient, zero, noplot, dens, douv, obType, custObserver, uvmin, uvmax);
}
}
}
return 0;
}