#ifndef lint static const char RCSid[] = "$Id: pabopto2xyz.c,v 2.3 2016/08/22 21:03:00 greg Exp $"; #endif /* * Combine PAB-Opto data files for color (CIE-XYZ) interpolation. * * G. Ward */ #define _USE_MATH_DEFINES #include "rtio.h" #include #include #include #include "interp2d.h" /* #define DEBUG 1 */ #ifndef MAX_INPUTS #define MAX_INPUTS 512 /* can handle this many files per chan */ #endif #define ANGRES 256 /* resolution per 180 degrees */ /* Input file header information */ typedef struct { const char *fname; /* input file path */ double theta, phi; /* input angles (degrees) */ double up_phi; /* azimuth for "up" direction */ int igp[2]; /* input grid position */ int isDSF; /* data is DSF (rather than BSDF)? */ int ndata; /* number of data points (or 0) */ long dstart; /* data start offset in file */ } PGINPUT; /* 2-D interpolant for a particular incident direction */ typedef struct { INTERP2 *ip2; /* 2-D interpolatiing function */ float *va; /* corresponding value array */ } PGINTERP; extern void SDdisk2square(double sq[2], double diskx, double disky); char *progname; /* global argv[0] */ int incident_side = 0; /* signum(intheta) */ int exiting_side = 0; /* signum(outtheta) */ int nprocs = 1; /* number of processes to run */ int nchild = 0; /* number of children (-1 in child) */ char *basename = "pabopto_xyz"; /* color conversion matrix */ double sens2xyz[3][3] = { 8.7539e-01, 3.5402e-02, 9.0381e-02, 1.3463e+00, -3.9082e-01, 7.2721e-02, 2.7306e-01, -2.1111e-01, 1.1014e+00, }; #if defined(_WIN32) || defined(_WIN64) #define await_children(n) (void)(n) #define run_subprocess() 0 #define end_subprocess() (void)0 #else /* ! _WIN32 */ #include #include /* Wait for the specified number of child processes to complete */ static void await_children(int n) { int exit_status = 0; if (n > nchild) n = nchild; while (n-- > 0) { int status; if (wait(&status) < 0) { fprintf(stderr, "%s: missing child(ren)!\n", progname); nchild = 0; break; } --nchild; if (status) { /* something wrong */ if ((status = WEXITSTATUS(status))) exit_status = status; else exit_status += !exit_status; fprintf(stderr, "%s: subprocess died\n", progname); n = nchild; /* wait for the rest */ } } if (exit_status) exit(exit_status); } /* Start child process if multiprocessing selected */ static pid_t run_subprocess(void) { int status; pid_t pid; if (nprocs <= 1) /* any children requested? */ return(0); await_children(nchild + 1 - nprocs); /* free up child process */ if ((pid = fork())) { if (pid < 0) { fprintf(stderr, "%s: cannot fork subprocess\n", progname); await_children(nchild); exit(1); } ++nchild; /* subprocess started */ return(pid); } nchild = -1; return(0); /* put child to work */ } /* If we are in subprocess, call exit */ #define end_subprocess() if (nchild < 0) _exit(0); else #endif /* ! _WIN32 */ /* Compute square location from normalized input/output vector */ static void sq_from_ang(double sq[2], double theta, double phi) { double vec[3]; double norm; /* uniform hemispherical projection */ vec[2] = sin(M_PI/180.*theta); vec[0] = cos(M_PI/180.*phi)*vec[2]; vec[1] = sin(M_PI/180.*phi)*vec[2]; vec[2] = sqrt(1. - vec[2]*vec[2]); norm = 1./sqrt(1. + vec[2]); SDdisk2square(sq, vec[0]*norm, vec[1]*norm); } /* Compute quantized grid position from normalized input/output vector */ static void pos_from_ang(int gp[2], double theta, double phi) { double sq[2]; sq_from_ang(sq, theta, phi); gp[0] = (int)(sq[0]*ANGRES); gp[1] = (int)(sq[1]*ANGRES); } /* Compare incident angles */ static int cmp_indir(const void *p1, const void *p2) { const PGINPUT *inp1 = (const PGINPUT *)p1; const PGINPUT *inp2 = (const PGINPUT *)p2; int ydif; if (!inp1->dstart) /* put terminator last */ return(inp2->dstart > 0); if (!inp2->dstart) return(-1); ydif = inp1->igp[1] - inp2->igp[1]; if (ydif) return(ydif); return(inp1->igp[0] - inp2->igp[0]); } /* Prepare a PAB-Opto input file by reading its header */ static int init_pabopto_inp(PGINPUT *pgi, const char *fname) { FILE *fp = fopen(fname, "r"); char buf[2048]; int c; if (fp == NULL) { fputs(fname, stderr); fputs(": cannot open\n", stderr); return(0); } pgi->fname = fname; pgi->isDSF = -1; pgi->ndata = 0; pgi->up_phi = 0; pgi->theta = pgi->phi = -10001.; /* read header information */ while ((c = getc(fp)) == '#' || c == EOF) { char typ[64]; if (fgets(buf, sizeof(buf), fp) == NULL) { fputs(fname, stderr); fputs(": unexpected EOF\n", stderr); fclose(fp); return(0); } if (sscanf(buf, "colorimetry: %s", typ) == 1) { if (!strcasecmp(typ, "CIE-XYZ")) { fputs(fname, stderr); fputs(": already in XYZ color space!\n", stderr); return(0); } continue; } if (sscanf(buf, "datapoints_in_file:%d", &pgi->ndata) == 1) continue; if (sscanf(buf, "format: theta phi %s", typ) == 1) { if (!strcasecmp(typ, "DSF")) pgi->isDSF = 1; else if (!strcasecmp(typ, "BSDF") || !strcasecmp(typ, "BRDF") || !strcasecmp(typ, "BTDF")) pgi->isDSF = 0; continue; } if (sscanf(buf, "upphi %lf", &pgi->up_phi) == 1) continue; if (sscanf(buf, "intheta %lf", &pgi->theta) == 1) continue; if (sscanf(buf, "inphi %lf", &pgi->phi) == 1) continue; if (sscanf(buf, "incident_angle %lf %lf", &pgi->theta, &pgi->phi) == 2) continue; } pgi->dstart = ftell(fp) - 1; fclose(fp); if (pgi->isDSF < 0) { fputs(fname, stderr); fputs(": unknown format\n", stderr); return(0); } if ((pgi->theta < -10000.) | (pgi->phi < -10000.)) { fputs(fname, stderr); fputs(": unknown incident angle\n", stderr); return(0); } if (!incident_side) { incident_side = (pgi->theta < 90.) ? 1 : -1; } else if ((incident_side > 0) ^ (pgi->theta < 90.)) { fputs(fname, stderr); fputs(": incident on opposite side of surface\n", stderr); return(0); } /* convert angle to grid position */ pos_from_ang(pgi->igp, pgi->theta, pgi->phi); return(1); } /* Load interpolating function for a particular incident direction */ static int load_interp(PGINTERP *pgint, const int igp[2], const PGINPUT *pginp) { int nv = 0; int nread = 0; pgint->ip2 = NULL; pgint->va = NULL; /* load input file(s) */ while (pginp->dstart && (pginp->igp[0] == igp[0]) & (pginp->igp[1] == igp[1])) { FILE *fp = fopen(pginp->fname, "r"); double theta, phi; float val; if (fp == NULL || fseek(fp, pginp->dstart, SEEK_SET) < 0) { fputs(pginp->fname, stderr); fputs(": cannot re-open input file!\n", stderr); return(0); } #ifdef DEBUG fprintf(stderr, "Loading channel from '%s'\n", pginp->fname); #endif while (fscanf(fp, "%lf %lf %f", &theta, &phi, &val) == 3) { double sq[2]; if (nread >= nv) { if (pginp->ndata > 0) nv += pginp->ndata; else nv += (nv>>1) + 1024; pgint->ip2 = interp2_realloc(pgint->ip2, nv); pgint->va = (float *)realloc(pgint->va, sizeof(float)*nv); if ((pgint->ip2 == NULL) | (pgint->va == NULL)) goto memerr; } if (!exiting_side) { exiting_side = (theta < 90.) ? 1 : -1; } else if ((exiting_side > 0) ^ (theta < 90.)) { fputs(pginp->fname, stderr); fputs(": exiting measurement on wrong side\n", stderr); return(0); } sq_from_ang(sq, theta, phi); pgint->ip2->spt[nread][0] = sq[0]*ANGRES; pgint->ip2->spt[nread][1] = sq[1]*ANGRES; pgint->va[nread++] = val; } fclose(fp); ++pginp; /* advance to next input */ } if (nv > nread) { /* fix array sizes */ pgint->ip2 = interp2_realloc(pgint->ip2, nread); pgint->va = (float *)realloc(pgint->va, nread); if ((pgint->ip2 == NULL) | (pgint->va == NULL)) goto memerr; } return(1); memerr: fputs(progname, stderr); fputs(": Out of memory in load_interp()!\n", stderr); return(0); } /* Interpolate value at the given position */ static double interp2val(const PGINTERP *pgint, double px, double py) { #define NSMP 36 float wt[NSMP]; int si[NSMP]; int n = interp2_topsamp(wt, si, NSMP, pgint->ip2, px, py); double v = .0; while (n-- > 0) v += wt[n]*pgint->va[si[n]]; return(v); #undef NSMP } static void free_interp(PGINTERP *pgint) { interp2_free(pgint->ip2); pgint->ip2 = NULL; free(pgint->va); pgint->va = NULL; } /* Interpolate the given incident direction to an output file */ static int interp_xyz(const PGINPUT *inp0, int nf, const PGINTERP *int1, const PGINTERP *int2) { int dtotal = 0; char outfname[256]; FILE *ofp; int i; if (nf <= 0) return(0); /* create output file & write header */ sprintf(outfname, "%s_t%03.0fp%03.0f.txt", basename, inp0->theta, inp0->phi); if ((ofp = fopen(outfname, "w")) == NULL) { fputs(outfname, stderr); fputs(": cannot open for writing\n", stderr); return(0); } fprintf(ofp, "#data written using %s\n", progname); fprintf(ofp, "#incident_angle %g %g\n", inp0->theta, inp0->phi); fprintf(ofp, "#intheta %g\n", inp0->theta); fprintf(ofp, "#inphi %g\n", inp0->phi); fprintf(ofp, "#upphi %g\n", inp0->up_phi); fprintf(ofp, "#colorimetry: CIE-XYZ\n"); for (i = nf; i--; ) dtotal += inp0[i].ndata; if (dtotal > 0) fprintf(ofp, "#datapoints_in_file: %d\n", dtotal); fprintf(ofp, "#format: theta phi %s\n", inp0->isDSF ? "DSF" : "BSDF"); dtotal = 0; while (nf-- > 0) { /* load channel 1 file(s) */ FILE *ifp = fopen(inp0->fname, "r"); double theta, phi, val[3], sq[2]; if (ifp == NULL || fseek(ifp, inp0->dstart, SEEK_SET) < 0) { fputs(inp0->fname, stderr); fputs(": cannot re-open input file!\n", stderr); return(0); } #ifdef DEBUG fprintf(stderr, "Adding points from '%s' to '%s'\n", inp0->fname, outfname); #endif while (fscanf(ifp, "%lf %lf %lf", &theta, &phi, &val[0]) == 3) { if (!exiting_side) { exiting_side = (theta < 90.) ? 1 : -1; } else if ((exiting_side > 0) ^ (theta < 90.)) { fputs(inp0->fname, stderr); fputs(": exiting measurement on wrong side\n", stderr); return(0); } sq_from_ang(sq, theta, phi); val[1] = interp2val(int1, sq[0]*ANGRES, sq[1]*ANGRES); val[2] = interp2val(int2, sq[0]*ANGRES, sq[1]*ANGRES); fprintf(ofp, "%.3f %.3f", theta, phi); for (i = 0; i < 3; i++) fprintf(ofp, " %.4f", sens2xyz[i][0]*val[0] + sens2xyz[i][1]*val[1] + sens2xyz[i][2]*val[2]); fputc('\n', ofp); ++dtotal; } fclose(ifp); ++inp0; /* advance to next input file */ } #ifdef DEBUG fprintf(stderr, "Wrote %d values to '%s'\n", dtotal, outfname); #endif return(fclose(ofp) == 0); } /* Do next incident direction and advance counters */ static int advance_incidence(PGINPUT *slist[3], int ndx[3]) { const int i0 = ndx[0]; PGINTERP pgi1, pgi2; int c; /* match up directions */ while ((c = cmp_indir(slist[1]+ndx[1], slist[0]+ndx[0])) < 0) ndx[1]++; if (c) { fputs(slist[0][ndx[0]].fname, stderr); fputs(": warning - missing channel 2 at this incidence\n", stderr); do ++ndx[0]; while (!cmp_indir(slist[0]+ndx[0], slist[0]+i0)); return(0); } while ((c = cmp_indir(slist[2]+ndx[2], slist[0]+ndx[0])) < 0) ndx[2]++; if (c) { fputs(slist[0][ndx[0]].fname, stderr); fputs(": warning - missing channel 3 at this incidence\n", stderr); do ++ndx[0]; while (!cmp_indir(slist[0]+ndx[0], slist[0]+i0)); return(0); } do /* advance first channel index */ ++ndx[0]; while (!cmp_indir(slist[0]+ndx[0], slist[0]+i0)); if (run_subprocess()) /* fork here if multiprocessing */ return(1); /* interpolate channels 2 & 3 */ load_interp(&pgi1, slist[0][i0].igp, slist[1]+ndx[1]); load_interp(&pgi2, slist[0][i0].igp, slist[2]+ndx[2]); if (!interp_xyz(slist[0]+i0, ndx[0]-i0, &pgi1, &pgi2)) return(-1); end_subprocess(); free_interp(&pgi1); free_interp(&pgi2); return(1); } /* Read in single-channel PAB-Opto BSDF files and output XYZ versions */ int main(int argc, char *argv[]) { char *flist[MAX_INPUTS]; PGINPUT *slist[3]; int i, j; int ndx[3]; /* get options */ progname = argv[0]; for (i = 1; i < argc-3 && argv[i][0] == '-'; i++) switch (argv[i][1]) { case 'n': nprocs = atoi(argv[++i]); break; case 'm': if (i >= argc-(3+9)) goto userr; for (j = 0; j < 3; j++) { sens2xyz[j][0] = atof(argv[++i]); sens2xyz[j][1] = atof(argv[++i]); sens2xyz[j][2] = atof(argv[++i]); } break; case 'o': basename = argv[++i]; break; default: goto userr; } if (i > argc-3) goto userr; for (j = 0; j < 3; j++) { /* prep input channels */ int k, n; n = wordfile(flist, MAX_INPUTS, argv[i+j]); if (n <= 0) { fputs(argv[i+j], stderr); fputs(": cannot load input file names\n", stderr); return(1); } slist[j] = (PGINPUT *)malloc(sizeof(PGINPUT)*(n+1)); if (slist[j] == NULL) { fputs(argv[0], stderr); fputs(": out of memory!\n", stderr); return(1); } #ifdef DEBUG fprintf(stderr, "Checking %d files from '%s'\n", n, argv[i+j]); #endif for (k = 0; k < n; k++) if (!init_pabopto_inp(slist[j]+k, flist[k])) return(1); memset(slist[j]+n, 0, sizeof(PGINPUT)); /* sort by incident direction */ qsort(slist[j], n, sizeof(PGINPUT), cmp_indir); } ndx[0]=ndx[1]=ndx[2]=0; /* compile measurements */ while (slist[0][ndx[0]].dstart) if (advance_incidence(slist, ndx) < 0) return(1); await_children(nchild); /* wait for all to finish */ return(0); userr: fputs("Usage: ", stderr); fputs(argv[0], stderr); fputs(" [-m X1 X2 X3 Y1 Y2 Y3 Z1 Z2 Z3][-o basename][-n nprocs]", stderr); fputs(" s1files.txt s2files.txt s3files.txt\n", stderr); return(1); }