ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/rt/rxtrace.cpp
Revision: 2.3
Committed: Tue Apr 30 23:16:23 2024 UTC (2 weeks, 3 days ago) by greg
Branch: MAIN
Changes since 2.2: +103 -51 lines
Log Message:
feat(rxtrace): Added multi-processing and spectral color

File Contents

# User Rev Content
1 greg 2.1 #ifndef lint
2 greg 2.3 static const char RCSid[] = "$Id: rxtrace.cpp,v 2.2 2024/03/12 16:54:51 greg Exp $";
3 greg 2.1 #endif
4     /*
5     * C++ module for individual ray tracing.
6     */
7    
8     /*
9     * Input is in the form:
10     *
11     * xorg yorg zorg xdir ydir zdir
12     *
13     * The direction need not be normalized. Output is flexible.
14     * If the direction vector is (0,0,0), then the output is flushed.
15     * All values default to ascii representation of real
16     * numbers. Binary representations can be selected
17     * with '-ff' for float or '-fd' for double. By default,
18     * radiance is computed. The '-i' or '-I' options indicate that
19     * irradiance values are desired.
20     */
21    
22     #include "copyright.h"
23    
24     #include "RtraceSimulManager.h"
25     #include "platform.h"
26     #include "otypes.h"
27     #include "otspecial.h"
28     #include "source.h"
29     #include "resolu.h"
30    
31     extern int inform; /* input format */
32     extern int outform; /* output format */
33    
34     extern char *tralist[]; /* list of modifers to trace (or no) */
35     extern int traincl; /* include == 1, exclude == 0 */
36    
37     extern int hresolu; /* horizontal resolution */
38     extern int vresolu; /* vertical resolution */
39    
40     extern int castonly; /* only doing ray-casting? */
41    
42 greg 2.3 extern double (*sens_curve)(SCOLOR scol); /* spectral conversion for 1-channel */
43     extern double out_scalefactor; /* output calibration scale factor */
44     extern RGBPRIMP out_prims; /* output color primitives (NULL if spectral) */
45    
46 greg 2.1 #ifndef MAXTSET
47     #define MAXTSET 8191 /* maximum number in trace set */
48     #endif
49     OBJECT traset[MAXTSET+1]={0}; /* trace include/exclude set */
50    
51     // global simulation manager
52     extern RtraceSimulManager myRTmanager;
53    
54     static FILE *inpfp = NULL; /* input stream pointer */
55    
56     static FVECT *inp_queue = NULL; /* ray input queue if flushing */
57     static int inp_qpos = 0; /* next ray to return */
58     static int inp_qend = 0; /* number of rays in this work group */
59    
60     typedef void putf_t(RREAL *v, int n);
61     static putf_t puta, putd, putf, putrgbe;
62    
63     typedef void oputf_t(RAY *r);
64     static oputf_t oputo, oputd, oputv, oputV, oputl, oputL, oputc, oputp,
65     oputr, oputR, oputx, oputX, oputn, oputN, oputs,
66     oputw, oputW, oputm, oputM, oputtilde;
67    
68     extern void tranotify(OBJECT obj);
69     static void tabin(RAY *r);
70     static RayReportCall ourtrace;
71     static RayReportCall printvals;
72    
73 greg 2.3 static void putscolor(COLORV *scol);
74    
75 greg 2.1 static oputf_t *ray_out[32], *every_out[32];
76     static putf_t *putreal;
77    
78     void
79     quit( /* quit program */
80     int code
81     )
82     {
83 greg 2.3 if (ray_pnprocs < 0)
84     _exit(code); /* avoid flush in child */
85    
86 greg 2.1 int ec = myRTmanager.Cleanup();
87    
88     if (ec) code = ec;
89    
90     exit(code);
91     }
92    
93     const char *
94     formstr( /* return format identifier */
95     int f
96     )
97     {
98     switch (f) {
99     case 'a': return("ascii");
100     case 'f': return("float");
101     case 'd': return("double");
102 greg 2.3 case 'c':
103     if (out_prims == NULL)
104     return(SPECFMT);
105     if (out_prims == xyzprims)
106     return(CIEFMT);
107     return(COLRFMT);
108 greg 2.1 }
109     return("unknown");
110     }
111    
112     static bool
113     getvec( /* get a vector from fp */
114     FVECT vec,
115     int fmt,
116     FILE *fp
117     )
118     {
119     static char buf[32];
120     float * vf = (float *)buf;
121     double * vd = (double *)buf;
122     int i;
123    
124     switch (fmt) {
125     case 'a': /* ascii */
126     for (i = 0; i < 3; i++) {
127     if (fgetword(buf, sizeof(buf), fp) == NULL ||
128     !isflt(buf))
129     return false;
130     vec[i] = atof(buf);
131     }
132     break;
133     #ifdef SMLFLT
134     case 'f': /* binary float */
135     if (getbinary(vec, sizeof(RREAL), 3, fp) != 3)
136     return false;
137     break;
138     case 'd': /* binary double */
139     if (getbinary(vd, sizeof(double), 3, fp) != 3)
140     return false;
141     VCOPY(vec, vd);
142     break;
143     #else
144     case 'f': /* binary float */
145     if (getbinary(vf, sizeof(float), 3, fp) != 3)
146     return false;
147     VCOPY(vec, vf);
148     break;
149     case 'd': /* binary double */
150     if (getbinary(vec, sizeof(RREAL), 3, fp) != 3)
151     return false;
152     break;
153     #endif
154     default:
155     error(CONSISTENCY, "botched input format");
156     }
157     return true;
158     }
159    
160     // read ray list from inpfp
161     static int
162     getrays(FVECT org_dir[], int n)
163     {
164     int nread = 0;
165    
166     while (n-- > 0) {
167     if (!getvec(org_dir[0], inform, inpfp) ||
168     !getvec(org_dir[1], inform, inpfp))
169     break;
170     ++nread;
171     if (IsZeroVec(org_dir[1]))
172     break;
173     org_dir += 2;
174     }
175     return nread;
176     }
177    
178     void
179     rtrace( /* trace rays from stdin or file */
180     char *fname,
181     int nproc
182     )
183     {
184     RNUMBER vcount = (hresolu > 1) ? (RNUMBER)hresolu*vresolu
185     : (RNUMBER)vresolu;
186     const int flushIntvl = (!vresolu | (hresolu <= 1)) * hresolu;
187     FVECT * ivbuf = (FVECT *)malloc(2*sizeof(FVECT) *
188     (flushIntvl + !flushIntvl));
189     if (ivbuf == NULL)
190     error(SYSTEM, "cannot allocate input vector buffer");
191     /* set up input */
192     if (fname == NULL)
193     inpfp = stdin;
194     else if ((inpfp = fopen(fname, "r")) == NULL) {
195     sprintf(errmsg, "cannot open input file \"%s\"", fname);
196     error(SYSTEM, errmsg);
197     }
198     if (inform != 'a')
199     SET_FILE_BINARY(inpfp);
200     /* set up output */
201 greg 2.2 if (castonly || every_out[0] != NULL) {
202 greg 2.1 nproc = 1; /* don't bother multiprocessing */
203 greg 2.2 } else if (nproc <= 0) { // need to get default for system?
204     nproc = myRTmanager.GetNCores() + nproc;
205     if (nproc <= 0) nproc = 1;
206     }
207 greg 2.1 if ((flushIntvl > 0) & (nproc > flushIntvl)) {
208     error(WARNING, "reducing number of processes to match flush interval");
209     nproc = flushIntvl;
210     }
211     nproc = myRTmanager.SetThreadCount(nproc);
212     if (ray_out[0])
213     myRTmanager.SetCookedCall(printvals);
214     if (every_out[0])
215     myRTmanager.SetTraceCall(ourtrace);
216     myRTmanager.rtFlags |= RTdoFIFO;
217 greg 2.3 #ifdef getc_unlocked
218     flockfile(inpfp); /* avoid lock/unlock overhead */
219     flockfile(stdout);
220     #endif
221 greg 2.1 if (hresolu > 0) { // print resolution string if appropriate
222     if (vresolu > 0)
223     fprtresolu(hresolu, vresolu, stdout);
224     else
225     fflush(stdout);
226     }
227     int n; /* process input rays */
228     bool pending = false;
229     while ((n = getrays(ivbuf, flushIntvl + !flushIntvl)) > 0) {
230     if ((vcount > 0) & (n > vcount)) {
231     error(WARNING, "extra ray(s) past end of input");
232     n = vcount;
233     } // put ray(s) into queue
234     if (myRTmanager.EnqueueBundle(ivbuf, n) < n)
235     error(USER, "ray queuing failure");
236     pending |= (n > 1); // time to flush output?
237     bool atZero = IsZeroVec(ivbuf[2*n-1]);
238     if (pending & (atZero | (n == flushIntvl))) {
239     if (!myRTmanager.FlushQueue())
240     error(USER, "ray flush error");
241     fflush(stdout);
242     pending = false;
243     } else
244     pending |= !atZero;
245     if (ferror(stdout))
246     error(SYSTEM, "write error");
247     if (vcount && !(vcount -= n)) /* check for end */
248     break;
249     }
250     if (vcount)
251     error(WARNING, feof(inpfp) ? "unexpected EOF on input" :
252     "input read error");
253     if (fflush(stdout) < 0)
254     error(SYSTEM, "write error");
255     if (fname != NULL) {
256     fclose(inpfp);
257     inpfp = NULL;
258     }
259     free(ivbuf);
260     }
261    
262     int
263     setrtoutput(const char *outvals) /* set up output tables, return #comp */
264     {
265     const char *vs = outvals;
266     oputf_t **table = ray_out;
267 greg 2.3 const int nco = (sens_curve != NULL) ? 1 :
268     (out_prims != NULL) ? 3 : NCSAMP;
269 greg 2.1 int ncomp = 0;
270    
271     if (!*vs)
272     error(USER, "empty output specification");
273    
274     switch (outform) { /* make sure (*putreal)() calls someone! */
275     case 'a': putreal = puta; break;
276     case 'f': putreal = putf; break;
277     case 'd': putreal = putd; break;
278     case 'c':
279     if (outvals[1] || !strchr("vrx", outvals[0]))
280     error(USER, "color format only with -ov, -or, -ox");
281     putreal = putrgbe; break;
282     default:
283     error(CONSISTENCY, "botched output format");
284     }
285     castonly = 1; /* sets castonly as side-effect */
286     do
287     switch (*vs) {
288     case 'T': /* trace sources */
289     myRTmanager.rtFlags |= RTtraceSources;
290     /* fall through */
291     case 't': /* trace */
292     if (!vs[1]) break;
293     *table = NULL;
294     table = every_out;
295     castonly = 0;
296     break;
297     case 'o': /* origin */
298     *table++ = oputo;
299     ncomp += 3;
300     break;
301     case 'd': /* direction */
302     *table++ = oputd;
303     ncomp += 3;
304     break;
305     case 'r': /* reflected contrib. */
306     *table++ = oputr;
307 greg 2.3 ncomp += nco;
308 greg 2.1 castonly = 0;
309     break;
310     case 'R': /* reflected distance */
311     *table++ = oputR;
312     ncomp++;
313     castonly = 0;
314     break;
315     case 'x': /* xmit contrib. */
316     *table++ = oputx;
317 greg 2.3 ncomp += nco;
318 greg 2.1 castonly = 0;
319     break;
320     case 'X': /* xmit distance */
321     *table++ = oputX;
322     ncomp++;
323     castonly = 0;
324     break;
325     case 'v': /* value */
326     *table++ = oputv;
327 greg 2.3 ncomp += nco;
328 greg 2.1 castonly = 0;
329     break;
330     case 'V': /* contribution */
331     *table++ = oputV;
332 greg 2.3 ncomp += nco;
333 greg 2.1 castonly = 0;
334     if (ambounce > 0 && (ambacc > FTINY || ambssamp > 0))
335     error(WARNING,
336     "-otV accuracy depends on -aa 0 -as 0");
337     break;
338     case 'l': /* effective distance */
339     *table++ = oputl;
340     ncomp++;
341     castonly = 0;
342     break;
343     case 'c': /* local coordinates */
344     *table++ = oputc;
345     ncomp += 2;
346     break;
347     case 'L': /* single ray length */
348     *table++ = oputL;
349     ncomp++;
350     break;
351     case 'p': /* point */
352     *table++ = oputp;
353     ncomp += 3;
354     break;
355     case 'n': /* perturbed normal */
356     *table++ = oputn;
357     ncomp += 3;
358     castonly = 0;
359     break;
360     case 'N': /* unperturbed normal */
361     *table++ = oputN;
362     ncomp += 3;
363     break;
364     case 's': /* surface */
365     *table++ = oputs;
366     ncomp++;
367     break;
368     case 'w': /* weight */
369     *table++ = oputw;
370     ncomp++;
371     break;
372     case 'W': /* coefficient */
373     *table++ = oputW;
374 greg 2.3 ncomp += nco;
375 greg 2.1 castonly = 0;
376     if (ambounce > 0 && (ambacc > FTINY) | (ambssamp > 0))
377     error(WARNING,
378     "-otW accuracy depends on -aa 0 -as 0");
379     break;
380     case 'm': /* modifier */
381     *table++ = oputm;
382     ncomp++;
383     break;
384     case 'M': /* material */
385     *table++ = oputM;
386     ncomp++;
387     break;
388     case '~': /* tilde */
389     *table++ = oputtilde;
390     break;
391     default:
392     sprintf(errmsg, "unrecognized output option '%c'", *vs);
393     error(USER, errmsg);
394     }
395     while (*++vs);
396    
397     *table = NULL;
398     if (*every_out != NULL)
399     ncomp = 0;
400     /* compatibility */
401     if ((do_irrad | (myRTmanager.rtFlags & RTimmIrrad)) && castonly)
402     error(USER, "-I+ and -i+ options require some value output");
403     for (table = ray_out; *table != NULL; table++) {
404     if ((*table == oputV) | (*table == oputW))
405     error(WARNING, "-oVW options require trace mode");
406     if ((do_irrad | (myRTmanager.rtFlags & RTimmIrrad)) &&
407     (*table == oputr) | (*table == oputR) |
408     (*table == oputx) | (*table == oputX))
409     error(WARNING, "-orRxX options incompatible with -I+ and -i+");
410     }
411     return(ncomp);
412     }
413    
414 greg 2.3 static int
415 greg 2.1 printvals( /* print requested ray values */
416     RAY *r, void *cd
417     )
418     {
419     oputf_t **tp;
420    
421     if (ray_out[0] == NULL)
422 greg 2.3 return 0;
423 greg 2.1 for (tp = ray_out; *tp != NULL; tp++)
424     (**tp)(r);
425     if (outform == 'a')
426     putchar('\n');
427 greg 2.3 return 1;
428 greg 2.1 }
429    
430     void
431     tranotify( /* record new modifier */
432     OBJECT obj
433     )
434     {
435     static int hitlimit = 0;
436     OBJREC *o = objptr(obj);
437     char **tralp;
438    
439     if (obj == OVOID) { /* starting over */
440     traset[0] = 0;
441     hitlimit = 0;
442     return;
443     }
444     if (hitlimit || !ismodifier(o->otype))
445     return;
446     for (tralp = tralist; *tralp != NULL; tralp++)
447     if (!strcmp(o->oname, *tralp)) {
448     if (traset[0] >= MAXTSET) {
449     error(WARNING, "too many modifiers in trace list");
450     hitlimit++;
451     return; /* should this be fatal? */
452     }
453     insertelem(traset, obj);
454     return;
455     }
456     }
457    
458 greg 2.3 static int
459 greg 2.1 ourtrace( /* print ray values */
460     RAY *r, void *cd
461     )
462     {
463     oputf_t **tp;
464    
465     if (every_out[0] == NULL)
466 greg 2.3 return 0;
467 greg 2.1 if (r->ro == NULL) {
468     if (traincl == 1)
469 greg 2.3 return 0;
470 greg 2.1 } else if (traincl != -1 && traincl != inset(traset, r->ro->omod))
471 greg 2.3 return 0;
472 greg 2.1 tabin(r);
473     for (tp = every_out; *tp != NULL; tp++)
474     (**tp)(r);
475     if (outform == 'a')
476     putchar('\n');
477 greg 2.3 return 1;
478 greg 2.1 }
479    
480     static void
481     tabin( /* tab in appropriate amount */
482     RAY *r
483     )
484     {
485     const RAY *rp;
486    
487     for (rp = r->parent; rp != NULL; rp = rp->parent)
488     putchar('\t');
489     }
490    
491     static void
492     oputo( /* print origin */
493     RAY *r
494     )
495     {
496     (*putreal)(r->rorg, 3);
497     }
498    
499     static void
500     oputd( /* print direction */
501     RAY *r
502     )
503     {
504     (*putreal)(r->rdir, 3);
505     }
506    
507     static void
508     oputr( /* print mirrored contribution */
509     RAY *r
510     )
511     {
512 greg 2.3 putscolor(r->mcol);
513 greg 2.1 }
514    
515     static void
516     oputR( /* print mirrored distance */
517     RAY *r
518     )
519     {
520     (*putreal)(&r->rmt, 1);
521     }
522    
523     static void
524     oputx( /* print unmirrored contribution */
525     RAY *r
526     )
527     {
528 greg 2.3 SCOLOR cdiff;
529    
530     copyscolor(cdiff, r->rcol);
531     sopscolor(cdiff, -=, r->mcol);
532 greg 2.1
533 greg 2.3 putscolor(cdiff);
534 greg 2.1 }
535    
536     static void
537     oputX( /* print unmirrored distance */
538     RAY *r
539     )
540     {
541     (*putreal)(&r->rxt, 1);
542     }
543    
544     static void
545     oputv( /* print value */
546     RAY *r
547     )
548     {
549 greg 2.3 putscolor(r->rcol);
550 greg 2.1 }
551    
552     static void
553     oputV( /* print value contribution */
554     RAY *r
555     )
556     {
557 greg 2.3 SCOLOR contr;
558 greg 2.1
559     raycontrib(contr, r, PRIMARY);
560 greg 2.3 smultscolor(contr, r->rcol);
561     putscolor(contr);
562 greg 2.1 }
563    
564     static void
565     oputl( /* print effective distance */
566     RAY *r
567     )
568     {
569     RREAL d = raydistance(r);
570    
571     (*putreal)(&d, 1);
572     }
573    
574     static void
575     oputL( /* print single ray length */
576     RAY *r
577     )
578     {
579     (*putreal)(&r->rot, 1);
580     }
581    
582     static void
583     oputc( /* print local coordinates */
584     RAY *r
585     )
586     {
587     (*putreal)(r->uv, 2);
588     }
589    
590     static RREAL vdummy[3] = {0.0, 0.0, 0.0};
591    
592     static void
593     oputp( /* print intersection point */
594     RAY *r
595     )
596     {
597     (*putreal)(r->rop, 3); /* set to ray origin if distant or no hit */
598     }
599    
600     static void
601     oputN( /* print unperturbed normal */
602     RAY *r
603     )
604     {
605     if (r->ro == NULL) { /* zero vector if clipped or no hit */
606     (*putreal)(vdummy, 3);
607     return;
608     }
609     if (r->rflips & 1) { /* undo any flippin' flips */
610     FVECT unrm;
611     unrm[0] = -r->ron[0];
612     unrm[1] = -r->ron[1];
613     unrm[2] = -r->ron[2];
614     (*putreal)(unrm, 3);
615     } else
616     (*putreal)(r->ron, 3);
617     }
618    
619     static void
620     oputn( /* print perturbed normal */
621     RAY *r
622     )
623     {
624     FVECT pnorm;
625    
626     if (r->ro == NULL) { /* clipped or no hit */
627     (*putreal)(vdummy, 3);
628     return;
629     }
630     raynormal(pnorm, r);
631     (*putreal)(pnorm, 3);
632     }
633    
634     static void
635     oputs( /* print name */
636     RAY *r
637     )
638     {
639     if (r->ro != NULL)
640     fputs(r->ro->oname, stdout);
641     else
642     putchar('*');
643     putchar('\t');
644     }
645    
646     static void
647     oputw( /* print weight */
648     RAY *r
649     )
650     {
651     RREAL rwt = r->rweight;
652    
653     (*putreal)(&rwt, 1);
654     }
655    
656     static void
657     oputW( /* print coefficient */
658     RAY *r
659     )
660     {
661 greg 2.3 SCOLOR contr;
662 greg 2.1 /* shadow ray not on source? */
663     if (r->rsrc >= 0 && source[r->rsrc].so != r->ro)
664 greg 2.3 scolorblack(contr);
665 greg 2.1 else
666     raycontrib(contr, r, PRIMARY);
667    
668 greg 2.3 putscolor(contr);
669 greg 2.1 }
670    
671     static void
672     oputm( /* print modifier */
673     RAY *r
674     )
675     {
676     if (r->ro != NULL)
677     if (r->ro->omod != OVOID)
678     fputs(objptr(r->ro->omod)->oname, stdout);
679     else
680     fputs(VOIDID, stdout);
681     else
682     putchar('*');
683     putchar('\t');
684     }
685    
686     static void
687     oputM( /* print material */
688     RAY *r
689     )
690     {
691     OBJREC *mat;
692    
693     if (r->ro != NULL) {
694     if ((mat = findmaterial(r->ro)) != NULL)
695     fputs(mat->oname, stdout);
696     else
697     fputs(VOIDID, stdout);
698     } else
699     putchar('*');
700     putchar('\t');
701     }
702    
703     static void
704     oputtilde( /* output tilde (spacer) */
705     RAY *r
706     )
707     {
708     fputs("~\t", stdout);
709     }
710    
711     static void
712     puta( /* print ascii value(s) */
713     RREAL *v, int n
714     )
715     {
716     if (n == 3) {
717     printf("%e\t%e\t%e\t", v[0], v[1], v[2]);
718     return;
719     }
720     while (n--)
721     printf("%e\t", *v++);
722     }
723    
724     static void
725     putd(RREAL *v, int n) /* output binary double(s) */
726     {
727     #ifdef SMLFLT
728     double da[3];
729     int i;
730    
731     if (n > 3)
732     error(INTERNAL, "code error in putd()");
733     for (i = n; i--; )
734     da[i] = v[i];
735     putbinary(da, sizeof(double), n, stdout);
736     #else
737     putbinary(v, sizeof(RREAL), n, stdout);
738     #endif
739     }
740    
741     static void
742     putf(RREAL *v, int n) /* output binary float(s) */
743     {
744     #ifndef SMLFLT
745     float fa[3];
746     int i;
747    
748     if (n > 3)
749     error(INTERNAL, "code error in putf()");
750     for (i = n; i--; )
751     fa[i] = v[i];
752     putbinary(fa, sizeof(float), n, stdout);
753     #else
754     putbinary(v, sizeof(RREAL), n, stdout);
755     #endif
756     }
757    
758     static void
759     putrgbe(RREAL *v, int n) /* output RGBE color */
760     {
761     COLR cout;
762    
763     if (n != 3)
764     error(INTERNAL, "putrgbe() not called with 3 components");
765     setcolr(cout, v[0], v[1], v[2]);
766     putbinary(cout, sizeof(cout), 1, stdout);
767     }
768 greg 2.3
769     static void
770     putscolor(COLORV *scol) /* output (spectral) color */
771     {
772     static COLORMAT xyz2myrgbmat;
773     SCOLOR my_scol;
774     COLOR col;
775     /* single channel output? */
776     if (sens_curve != NULL) {
777     RREAL v = (*sens_curve)(scol) * out_scalefactor;
778     (*putreal)(&v, 1);
779     return;
780     }
781     if (out_scalefactor != 1.) { /* apply scalefactor if any */
782     copyscolor(my_scol, scol);
783     scalescolor(my_scol, out_scalefactor);
784     scol = my_scol;
785     }
786     if (out_prims == NULL) { /* full spectral reporting */
787     if (outform == 'c') {
788     SCOLR sclr;
789     scolor_scolr(sclr, scol);
790     putbinary(sclr, LSCOLR, 1, stdout);
791     } else if (sizeof(RREAL) != sizeof(COLORV)) {
792     RREAL sreal[MAXCSAMP];
793     int i = NCSAMP;
794     while (i--) sreal[i] = scol[i];
795     (*putreal)(sreal, NCSAMP);
796     } else
797     (*putreal)((RREAL *)scol, NCSAMP);
798     return;
799     }
800     if (out_prims == xyzprims) {
801     scolor_cie(col, scol);
802     } else if (out_prims == stdprims) {
803     scolor_rgb(col, scol);
804     } else {
805     COLOR xyz;
806     if (xyz2myrgbmat[0][0] == 0)
807     compxyz2rgbWBmat(xyz2myrgbmat, out_prims);
808     scolor_cie(xyz, scol);
809     colortrans(col, xyz2myrgbmat, xyz);
810     clipgamut(col, xyz[CIEY], CGAMUT_LOWER, cblack, cwhite);
811     }
812     if (outform == 'c') {
813     COLR clr;
814     setcolr(clr, colval(col,RED), colval(col,GRN), colval(col,BLU));
815     putbinary(clr, sizeof(COLR), 1, stdout);
816     } else if (sizeof(RREAL) != sizeof(COLORV)) {
817     RREAL creal[3];
818     copycolor(creal, col);
819     (*putreal)(creal, 3);
820     } else
821     (*putreal)((RREAL *)col, 3);
822     }