ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/rt/RpictSimulManager.cpp
Revision: 2.1
Committed: Wed Aug 14 20:05:23 2024 UTC (8 months, 2 weeks ago) by greg
Branch: MAIN
Log Message:
feat(rxpict): Added new C++ picture rendering tool with multi-processing and spectral output

File Contents

# User Rev Content
1 greg 2.1 #ifndef lint
2     static const char RCSid[] = "$Id$";
3     #endif
4     /*
5     * RpictSimulManager.cpp
6     *
7     * Rpict simulation manager implementation
8     *
9     * Created by Greg Ward on 07/11/2024.
10     */
11    
12     #include <ctype.h>
13     #include "platform.h"
14     #include "RpictSimulManager.h"
15     #include "depthcodec.h"
16     #include "random.h"
17    
18     /************* Imported globals from rxpmain.c *************/
19    
20     extern VIEW ourview; /* viewing parameters */
21     extern int hres, vres; /* current image resolution */
22    
23     extern int psample; /* pixel sample size */
24     extern double maxdiff; /* max. sample difference */
25     extern double dstrpix; /* square pixel distribution */
26    
27     extern double mblur; /* motion blur parameter */
28    
29     extern double dblur; /* depth-of-field blur parameter */
30    
31     // Assign a pixel value (& depth) from rendered ray value
32     bool
33     PixelAccess::SetPixel(int x, int y, const RAY *rp)
34     {
35     if (!rp) return false;
36    
37     COLOR col;
38     float zv = 0; // get depth if needed
39     if (DepthType())
40     zv = raydistance(rp);
41    
42     switch (ColorSpace()) {
43     case RDTscolor: // keeping rendered spectrum?
44     case RDTscolr:
45     return SetPixel(x, y, rp->rcol, zv);
46     case RDTrgb:
47     case RDTrgbe:
48     case RDTxyz:
49     case RDTxyze:
50     scolor_out(col, primp, rp->rcol);
51     return SetPixel(x, y, col, zv);
52     default:
53     error(INTERNAL, "missing color space type in SetPixel()");
54     }
55     return false;
56     }
57    
58     // Set color space after non-empty initialization
59     bool
60     PixelAccess::SetColorSpace(RenderDataType cs, RGBPRIMP pr)
61     {
62     if (!dtyp) return false;
63    
64     if (!(cs = RDTcolorT(cs)))
65     cs = RDTcolorT(dtyp);
66     else if (RDTcommonE(cs) ^ RDTcommonE(dtyp))
67     return false;
68    
69     if (NCSAMP == 3) {
70     if (cs == RDTscolr) cs = RDTrgbe;
71     else if (cs == RDTscolor) cs = RDTrgb;
72     }
73     switch (cs) {
74     case RDTxyze:
75     case RDTxyz:
76     primp = xyzprims;
77     break;
78     case RDTrgbe:
79     case RDTrgb:
80     primp = pr ? pr : stdprims;
81     break;
82     default: // RDTscolr | RDTscolor
83     primp = NULL;
84     break;
85     }
86     dtyp = RDTnewCT(dtyp, cs);
87     if (primp)
88     xyz2myrgbmat[0][0] = 0;
89     return true;
90     }
91    
92     /*
93     * Set up rendering frame (call after octree loaded)
94     * Overall dimensions may be adjusted for view,
95     * optional pixel aspect ratio and tile grid
96     * Increments frameNo if >0
97     */
98     bool
99     RpictSimulManager::NewFrame(const VIEW &v, int xydim[2], double *ap, const int *tgrid)
100     {
101     double pasp = 1.;
102    
103     if (!xydim) return false;
104     if (!ap) ap = &pasp;
105     pvw = vw; // save previous view for motion blur
106     vw = v;
107     const char * verr = setview(&vw);
108     if (verr) {
109     error(WARNING, verr);
110     vw = pvw;
111     return false;
112     }
113     const double va = viewaspect(&vw);
114     normaspect(va, ap, &xydim[0], &xydim[1]);
115     // set up tiling?
116     if (tgrid && (tgrid[0] > 0) & (tgrid[1] > 0) & (tgrid[0]*tgrid[1] > 1)) {
117     if ((8*tgrid[0] >= xydim[0]) | (8*tgrid[1] >= xydim[1])) {
118     error(WARNING, "Excessive tiling for image size");
119     return false;
120     }
121     xydim[0] -= xydim[0] % (tgsize[0] = tgrid[0]);
122     xydim[1] -= xydim[1] % (tgsize[1] = tgrid[1]);
123     *ap = va * xydim[0] / xydim[1];
124     } else
125     tgsize[0] = tgsize[1] = 1;
126    
127     if (vw.vaft > FTINY) rtFlags |= RTlimDist;
128     else rtFlags &= ~RTlimDist;
129     hvres[0] = xydim[0]; hvres[1] = xydim[1];
130     thvres[0] = hvres[0]/tgsize[0]; // presumed tile width
131     thvres[1] = hvres[1]/tgsize[1]; // ...and height
132     frameNo += (frameNo > 0); // caller may override after
133     return true;
134     }
135    
136     // Call-back for rendered pixel
137     int
138     RpictSimulManager::RtCall(RAY *r, void *cd)
139     {
140     RpictSimulManager * rsp = (RpictSimulManager *)cd;
141     const int ty = r->rno / rsp->TWidth();
142     const int tx = r->rno - (RNUMBER)ty*rsp->TWidth();
143    
144     if (ty >= rsp->THeight()) {
145     error(INTERNAL, "bad pixel calculation position in RtCall()");
146     return -1;
147     }
148     if (!rsp->doneMap.TestAndSet(tx, ty)) {
149     error(WARNING, "duplicate pixel calculation");
150     return 0;
151     }
152     return rsp->pacc.SetPixel(tx, ty, r);
153     }
154    
155     // Set up the specified tile (or entire image if NULL)
156     bool
157     RpictSimulManager::SetTile(const int ti[2])
158     {
159     tvw = vw; ptvw = pvw;
160    
161     if (ti) {
162     if ((ti[0] < 0) | (ti[0] >= tgsize[0]) |
163     (ti[1] < 0) | (ti[1] >= tgsize[1])) {
164     error(INTERNAL, "illegal tile specification in SetTile()");
165     return false;
166     }
167     const char * verr = cropview(&tvw,
168     (double)ti[0]/tgsize[0],
169     (double)ti[1]/tgsize[1],
170     (ti[0]+1.)/tgsize[0],
171     (ti[1]+1.)/tgsize[1]);
172     if (verr) {
173     sprintf(errmsg, "crop failure @ tile (%d,%d)/(%d,%d): %s",
174     ti[0], ti[1], tgsize[0], tgsize[1], verr);
175     error(USER, errmsg);
176     return false;
177     } // previous tile view for blur
178     if (!ptvw.type | (mblur <= FTINY) ||
179     cropview(&ptvw, (double)ti[0]/tgsize[0],
180     (double)ti[1]/tgsize[1],
181     (ti[0]+1.)/tgsize[0],
182     (ti[1]+1.)/tgsize[1]))
183     ptvw.type = 0;
184    
185     } else if ((tgsize[0] > 1) | (tgsize[1] > 1)) {
186     error(INTERNAL, "missing tile specification in SetTile()");
187     return false;
188     }
189     return doneMap.NewBitMap(TWidth(), THeight());
190     }
191    
192     #define pixjitter() (.5+dstrpix*(.5-frandom()))
193    
194     // Send the indicated pixel to ray tracer
195     bool
196     RpictSimulManager::ComputePixel(int x, int y)
197     {
198     static const SCOLOR scBlack = {0};
199     int i;
200     FVECT rodir[2];
201     double hpos = (x+pixjitter())/TWidth();
202     double vpos = (y+pixjitter())/THeight();
203     double dlim = viewray(rodir[0], rodir[1], &tvw, hpos, vpos);
204     if (dlim < -FTINY) { // off view?
205     pacc.SetPixel(x, y, scBlack);
206     doneMap.Set(x, y);
207     return true;
208     }
209     if (ptvw.type) { // add motion blur if requested
210     FVECT rorg2, rdir2;
211     double dlim2 = viewray(rorg2, rdir2, &ptvw, hpos, vpos);
212     if (dlim2 >= -FTINY) {
213     const double d = mblur*(.5-frandom());
214     dlim = (1.-d)*dlim + d*dlim2;
215     for (i = 3; i--; ) {
216     rodir[0][i] = (1.-d)*rodir[0][i] + d*rorg2[i];
217     rodir[1][i] = (1.-d)*rodir[1][i] + d*rdir2[i];
218     }
219     if (normalize(rodir[1]) == 0)
220     return false;
221     }
222     }
223     // depth-of-field blur if any
224     if (!jitteraperture(rodir[0], rodir[1], &tvw, dblur))
225     return false;
226     // include aft clipping distance?
227     for (i = (dlim > FTINY)*3; i--; )
228     rodir[1][i] *= dlim;
229    
230     return EnqueueRay(rodir[0], rodir[1], (RNUMBER)y*TWidth()+x);
231     }
232    
233     // Check if neighbor differences are below pixel sampling threshold
234     bool
235     RpictSimulManager::BelowSampThresh(int x, int y, const int noff[4][2]) const
236     {
237     SCOLOR pval[4];
238     float dist[4];
239     int i, j;
240    
241     for (i = 4; i--; ) { // get pixels from tile store
242     int px = x + noff[i][0];
243     int py = y + noff[i][1];
244     if (!doneMap.Check(px, py) ||
245     !pacc.GetPixel(px, py, pval[i], &dist[i]))
246     return false;
247     }
248     const bool spectr = (pacc.NC() > 3);
249     for (i = 4; --i; ) // do pairwise comparisons
250     for (j = i; j--; ) {
251     if (pacc.DepthType() &&
252     (dist[i] - dist[j] > maxdiff*dist[j]) |
253     (dist[j] - dist[i] > maxdiff*dist[i]))
254     return false;
255     if (spectr ? sbigsdiff(pval[i], pval[j], maxdiff) :
256     bigdiff(pval[i], pval[j], maxdiff))
257     return false;
258     }
259     return true; // linear interpolation OK
260     }
261    
262     // Fill an interior square patch with interpolated values
263     void
264     RpictSimulManager::FillSquare(int x, int y, const int noff[4][2])
265     {
266     SCOLOR pval[4];
267     float dist[4];
268     int i, j;
269     // assumes 4 corners are valid!
270     for (i = 4; i--; )
271     pacc.GetPixel(x+noff[i][0], y+noff[i][1], pval[i], &dist[i]);
272    
273     i = abs(noff[1][0]-noff[0][0]);
274     j = abs(noff[1][1]-noff[0][1]);
275     const int slen = i > j ? i : j;
276     const double sf = 1./slen;
277     const bool spectr = (pacc.NC() > 3);
278     for (i = 0; i <= slen; i++) { // bilinear interpolant
279     const double c1 = i*sf;
280     for (j = 0; j <= slen; j++) {
281     const double c2 = j*sf;
282     const int px = int(x + (1.-c1)*(1.-c2)*noff[0][0] +
283     c1*(1.-c2)*noff[1][0] +
284     (1.-c1)*c2*noff[2][0] +
285     c1*c2*noff[3][0] + .5);
286     const int py = int(y + (1.-c1)*(1.-c2)*noff[0][1] +
287     c1*(1.-c2)*noff[1][1] +
288     (1.-c1)*c2*noff[2][1] +
289     c1*c2*noff[3][1] + .5);
290     if (!doneMap.TestAndSet(px, py))
291     continue;
292     float zval = 0;
293     if (pacc.DepthType())
294     zval = (1.-c1)*(1.-c2)*dist[0] + c1*(1.-c2)*dist[1] +
295     (1.-c1)*c2*dist[2] + c1*c2*dist[3];
296     if (spectr) { // XXX assumes pacc.NC() == NCSAMP
297     SCOLOR ipval, tpval;
298     copyscolor(ipval, pval[0]);
299     scalescolor(ipval, (1.-c1)*(1.-c2));
300     copyscolor(tpval, pval[1]);
301     scalescolor(tpval, c1*(1.-c2));
302     saddscolor(ipval, tpval);
303     copyscolor(tpval, pval[2]);
304     scalescolor(tpval, (1.-c1)*c2);
305     saddscolor(ipval, tpval);
306     copyscolor(tpval, pval[3]);
307     scalescolor(tpval, c1*c2);
308     saddscolor(ipval, tpval);
309     pacc.SetPixel(px, py, ipval, zval);
310     } else { // tristimulus interpolation
311     COLOR ipval, tpval;
312     copycolor(ipval, pval[0]);
313     scalecolor(ipval, (1.-c1)*(1.-c2));
314     copycolor(tpval, pval[1]);
315     scalecolor(tpval, c1*(1.-c2));
316     addcolor(ipval, tpval);
317     copycolor(tpval, pval[2]);
318     scalecolor(tpval, (1.-c1)*c2);
319     addcolor(ipval, tpval);
320     copycolor(tpval, pval[3]);
321     scalecolor(tpval, c1*c2);
322     addcolor(ipval, tpval);
323     pacc.SetPixel(px, py, ipval, zval);
324     }
325     }
326     }
327     }
328    
329     // helper function to set up quincunx sampling
330     static void
331     SetQuincunx(ABitMap2 *bmp2, int noff[4][2], const int spc, const bool odd)
332     {
333     for (int y = 0; y < bmp2->Height(); y += spc>>1)
334     for (int x = odd*(spc>>1); x < bmp2->Width(); x += spc)
335     bmp2->Set(x, y);
336     // order neighbors CCW
337     if (odd) {
338     noff[0][0] = spc>>1; noff[0][1] = 0;
339     noff[1][0] = 0; noff[1][1] = spc>>1;
340     noff[2][0] = -(spc>>1); noff[2][1] = 0;
341     noff[3][0] = 0; noff[3][1] = -(spc>>1);
342     } else {
343     noff[0][0] = spc>>1; noff[0][1] = spc>>1;
344     noff[1][0] = -(spc>>1); noff[1][1] = spc>>1;
345     noff[2][0] = -(spc>>1); noff[2][1] = -(spc>>1);
346     noff[3][0] = spc>>1; noff[3][1] = -(spc>>1);
347     }
348     }
349    
350     // Render (or finish rendering) current tile
351     bool
352     RpictSimulManager::RenderRect()
353     {
354     if (!tvw.type || !Ready()) {
355     error(INTERNAL, "need octree and view for RenderRect()");
356     return false;
357     }
358     ABitMap2 doneSamples = doneMap;
359     int sp2 = ceil(log2((TWidth()>THeight() ? TWidth() : THeight()) - 1.));
360     int layer = 0;
361     int x, y;
362     fprintf(stderr, "Rendering %dx%d tile with psample=%d, maxdiff=%.3f ...\n",
363     TWidth(), THeight(), psample, maxdiff);
364     while (sp2 > 0) {
365     ABitMap2 sampMap(TWidth(), THeight());
366     int noff[4][2];
367     if ((prCB != NULL) & (barPix == NULL))
368     (*prCB)(100.*doneMap.SumTotal()/doneMap.Width()/doneMap.Height());
369     SetQuincunx(&sampMap, noff, 1<<sp2, layer&1);
370     sampMap -= doneSamples; // avoid resampling pixels
371     // Are we into adaptive sampling realm?
372     if (noff[0][0]*noff[0][0] + noff[0][1]*noff[0][1] < psample*psample) {
373     if (FlushQueue() < 0) // need results to check threshold
374     return false;
375     ABitMap2 fillMap = sampMap;
376     for (x = y = 0; sampMap.Find(&x, &y); x++)
377     if (BelowSampThresh(x, y, noff))
378     sampMap.Reset(x, y);
379     // spread sampling to neighbors...
380     const ABitMap2 origSampMap = sampMap;
381     for (int yoff = -(1<<(sp2-1));
382     yoff <= 1<<(sp2-1); yoff += 1<<sp2)
383     for (int xoff = -(1<<(sp2-1));
384     xoff <= 1<<(sp2-1); xoff += 1<<sp2) {
385     ABitMap2 stamp = origSampMap;
386     stamp.Shift(xoff, yoff);
387     sampMap |= stamp;
388     } // ...but don't resample what's done
389     sampMap -= doneSamples;
390     // interpolate smooth regions
391     fillMap -= sampMap;
392     for (x = y = 0; fillMap.Find(&x, &y); x++)
393     FillSquare(x, y, noff);
394     doneSamples |= doneMap;
395     } // compute required ray samples
396     for (x = y = 0; sampMap.Find(&x, &y); x++)
397     if (!ComputePixel(x, y))
398     return false;
399     doneSamples |= sampMap; // samples now done or at least queued
400     fprintf(stderr, "Sampled %ld pixels at (sp2,layer)=(%d,%d)\n",
401     (long)sampMap.SumTotal(), sp2, layer);
402     fprintf(stderr, "\t%ld pixels (%.3f%%) completed (+%ld in process)\n",
403     (long)doneMap.SumTotal(), 100.*doneMap.SumTotal()/doneMap.Width()/doneMap.Height(),
404     (long)(doneSamples.SumTotal()-doneMap.SumTotal()));
405     sp2 -= layer++ & 1; // next denser sampling
406     }
407     if (FlushQueue() < 0) // make sure we got everyone
408     return false;
409     x = y = 0;
410     if (doneMap.Find(&x, &y, false)) {
411     sprintf(errmsg, "missed %ld tile pixels, e.g. (%d,%d)",
412     (long)doneMap.Width()*doneMap.Height() -
413     doneMap.SumTotal(), x, y);
414     error(WARNING, errmsg);
415     }
416     if ((prCB != NULL) & (barPix == NULL))
417     (*prCB)(100.);
418     return true;
419     }
420    
421     /*
422     * Render the specified tile in frame
423     * Tile pixels are contiguous unless ystride != 0
424     * Tiles numbered from upper-left at (0,0)
425     * Pixel type influenced by this->prims assignment
426     */
427     bool
428     RpictSimulManager::RenderTile(COLORV *rp, int ystride, float *zp, const int *tile)
429     {
430     if (!rp | (GetWidth() <= 0) | (GetHeight() <= 0) | !vw.type)
431     return false;
432     if (!ystride) // contiguous rows?
433     ystride = TWidth();
434     pacc.Init(rp, ystride, zp);
435     if (prims == xyzprims)
436     pacc.SetColorSpace(RDTxyz);
437     else if (prims)
438     pacc.SetColorSpace(RDTrgb, prims);
439    
440     return SetTile(tile) && RenderRect();
441     }
442    
443     // Same but store as common-exponent COLR or SCOLR
444     bool
445     RpictSimulManager::RenderTile(COLRV *bp, int ystride, float *zp, const int *tile)
446     {
447     if (!bp | (GetWidth() <= 0) | (GetHeight() <= 0) | !vw.type)
448     return false;
449     if (!ystride) // contiguous rows?
450     ystride = TWidth();
451     pacc.Init(bp, ystride, zp);
452     if (prims == xyzprims)
453     pacc.SetColorSpace(RDTxyze);
454     else if (prims)
455     pacc.SetColorSpace(RDTrgbe, prims);
456    
457     return SetTile(tile) && RenderRect();
458     }
459    
460     // Same but also use 16-bit encoded depth buffer
461     bool
462     RpictSimulManager::RenderTile(COLRV *bp, int ystride, short *dp, const int *tile)
463     {
464     if (!bp | (GetWidth() <= 0) | (GetHeight() <= 0) | !vw.type)
465     return false;
466     if (!ystride) // contiguous rows?
467     ystride = TWidth();
468     pacc.Init(bp, ystride, dp);
469     if (prims == xyzprims)
470     pacc.SetColorSpace(RDTxyze);
471     else if (prims)
472     pacc.SetColorSpace(RDTrgbe, prims);
473    
474     return SetTile(tile) && RenderRect();
475     }
476    
477     // Allocate a new render bar
478     void
479     RpictSimulManager::NewBar(int ht)
480     {
481     delete [] barPix;
482     delete [] barDepth;
483     if (ht > GetHeight()) ht = GetHeight();
484     if ((ht <= 0) | (GetWidth() <= 0)) {
485     doneMap.NewBitMap(0,0);
486     pacc.Init();
487     barPix = NULL; barDepth = NULL;
488     return;
489     }
490     thvres[0] = GetWidth();
491     thvres[1] = ht;
492     const int NC = prims ? 3 : NCSAMP;
493     barPix = new COLORV [ht*thvres[0]*NC];
494     barDepth = new float [ht*thvres[0]];
495     pacc.Init(barPix + (ht-1)*thvres[0]*NC,
496     -thvres[0], barDepth + (ht-1)*thvres[0]);
497     if (prims == xyzprims)
498     pacc.SetColorSpace(RDTxyz);
499     else if (prims)
500     pacc.SetColorSpace(RDTrgb, prims);
501    
502     doneMap.NewBitMap(TWidth(), THeight());
503     }
504    
505     // Shift render bar area the specified amount down the frame
506     bool
507     RpictSimulManager::LowerBar(int v)
508     {
509     if (v <= 0) return !v;
510     if (!barPix | !barDepth | (v > THeight()) | !tvw.type)
511     return false;
512     tvw.voff -= double(v)/GetHeight();
513     ptvw.voff -= double(v)/GetHeight();
514     if (v == THeight()) {
515     doneMap.ClearBitMap();
516     return true;
517     }
518     const int NC = pacc.NC();
519     doneMap.Shift(0, v, false); // lift finished pixel samples
520     memmove(barPix, barPix + NC*TWidth()*v,
521     sizeof(COLORV)*NC*TWidth()*(THeight()-v));
522     memmove(barDepth, barDepth + TWidth()*v,
523     sizeof(float)*TWidth()*(THeight()-v));
524     return true;
525     }
526    
527     // Continue rendering from the specified position
528     bool
529     RpictSimulManager::RenderBelow(int ytop, const int vstep, FILE *pfp, const int dt, FILE *dfp)
530     {
531     if (ytop <= 0)
532     return true;
533     ptvw = pvw; // set starting bar's view
534     tvw = vw;
535     const char * verr = cropview(&tvw, 0., double(ytop-THeight())/GetHeight(),
536     1., double(ytop)/GetHeight());
537     if (verr) {
538     sprintf(errmsg, "illegal render bar below y=%d: %s", ytop, verr);
539     error(INTERNAL, errmsg);
540     return false;
541     }
542     if (!ptvw.type | (mblur <= FTINY) ||
543     cropview(&ptvw, 0., double(ytop-THeight())/GetHeight(),
544     1., double(ytop)/GetHeight()))
545     ptvw.type = 0;
546     // set up spectral sampling
547     if (setspectrsamp(CNDX, WLPART) <= 0) {
548     error(USER, "unsupported spectral sampling");
549     return false;
550     }
551     COLORV ** parr = NULL; // set up tiny source drawing
552     float ** zarr = NULL;
553     if (!ptvw.type && directvis && dblur <= FTINY) {
554     parr = new COLORV * [THeight()];
555     zarr = new float * [THeight()];
556     for (int n = THeight(); n-- > 0; ) {
557     parr[THeight()-1-n] = barPix + pacc.NC()*TWidth()*n;
558     zarr[THeight()-1-n] = barDepth + TWidth()*n;
559     }
560     ourview = vw; hres = GetWidth(); vres = GetHeight();
561     init_drawsources(psample);
562     }
563     int lastOut = ytop; // render down frame
564     while (ytop > 0) {
565     if (ytop < THeight()) // mark what we won't do as finished
566     doneMap.ClearRect(0, 0, TWidth(), THeight()-ytop, true);
567     if (prCB)
568     (*prCB)(100.*(GetHeight()-ytop)/GetHeight());
569     if (!RenderRect()) // render this bar
570     return false;
571     int nlines = lastOut - ytop + THeight();
572     if (nlines > ytop)
573     nlines = ytop;
574     else if (parr) // drawing sources?
575     drawsources(parr, prims, zarr,
576     0, hres, lastOut-nlines, nlines);
577    
578     if (dfp) { // write out depth scanlines?
579     const float * dp = barDepth + TWidth()*(ytop-lastOut);
580     if (RDTdepthT(dt) == RDTdshort) {
581     for (int n = TWidth()*nlines; n-- > 0; dp++)
582     if (putint(depth2code(*dp, pacc.refDepth), 2, dfp) == EOF)
583     error(SYSTEM, "cannot write 16-bit depth buffer");
584     } else if (putbinary(dp, sizeof(float), TWidth()*nlines, dfp)
585     != TWidth()*nlines)
586     error(SYSTEM, "cannot write raw depth buffer");
587     }
588     COLORV * bpos = barPix + pacc.NC()*TWidth()*(ytop-lastOut);
589     while (nlines-- > 0) { // write pixel scanlines
590     switch (RDTcolorT(dt)) {
591     case RDTrgbe:
592     case RDTxyze:
593     if (fwritescan((COLOR *)bpos, TWidth(), pfp) < 0)
594     error(SYSTEM, "cannot write RGBE/XYZE output");
595     break;
596     case RDTscolr:
597     if (fwritesscan(bpos, pacc.NC(), TWidth(), pfp) < 0)
598     error(SYSTEM, "cannot write SCOLOR output");
599     break;
600     case RDTrgb:
601     case RDTxyz:
602     case RDTscolor:
603     if (putbinary(bpos, sizeof(COLORV)*pacc.NC(), TWidth(), pfp)
604     != TWidth())
605     error(SYSTEM, "cannot write SCOLOR output");
606     break;
607     default:
608     error(INTERNAL, "missing output color type in RenderBelow()");
609     break;
610     }
611     bpos += pacc.NC()*TWidth();
612     --lastOut;
613     } // flush each scan bar
614     if (fflush(pfp) == EOF || (dfp && fflush(dfp) == EOF))
615     error(SYSTEM, "output write error");
616     // advance down the frame
617     if (lastOut > 0 && !LowerBar(vstep))
618     return false;
619     ytop -= vstep;
620     }
621     delete [] parr;
622     delete [] zarr;
623     if (prCB)
624     (*prCB)(100.);
625     return true;
626     }
627    
628     /*
629     * Render and write a frame to the named file
630     * Include any header lines set prior to call
631     * Picture file must not already exist
632     * Picture to stdout if pfname==NULL
633     * Depth written to a command if dfname[0]=='!'
634     */
635     RenderDataType
636     RpictSimulManager::RenderFrame(const char *pfname, RenderDataType dt, const char *dfname)
637     {
638     int fd = 1;
639     FILE * pfp = NULL;
640     FILE * dfp = NULL;
641    
642     if (!RDTcolorT(dt))
643     error(INTERNAL, "missing pixel output type in RenderFrame()");
644     if (NCSAMP == 3) {
645     if (RDTcolorT(dt) == RDTscolr)
646     dt = RDTnewCT(dt, prims==xyzprims ? RDTxyze : RDTrgbe);
647     else if (RDTcolorT(dt) == RDTscolor)
648     dt = RDTnewCT(dt, prims==xyzprims ? RDTxyz : RDTrgb);
649     }
650     if (!RDTdepthT(dt) ^ !dfname)
651     error(INTERNAL, "depth output requires file name and type in RenderFrame()");
652     if (pfname) { // open picture output file
653     if (pfname[0] == '!') {
654     error(INTERNAL, "writing picture to a command not supported");
655     return RDTnone;
656     }
657     fd = open(pfname, O_WRONLY|O_CREAT|O_EXCL, 0666);
658     }
659     if (fd < 0) {
660     if ((frameNo <= 0) | (errno != EEXIST)) {
661     sprintf(errmsg, "cannot open picture file '%s'", pfname);
662     error(SYSTEM, errmsg);
663     }
664     return RDTnone; // expected in parallel sequence
665     }
666     if (fd == 1)
667     pfp = stdout;
668     else if (!(pfp = fdopen(fd, "w")))
669     error(SYSTEM, "failure calling fdopen()");
670     SET_FILE_BINARY(pfp); // write picture header
671     if ((pfp != stdout) | (frameNo <= 1)) {
672     newheader("RADIANCE", pfp);
673     fputs(GetHeader(), pfp);
674     }
675     fputs(VIEWSTR, pfp); fprintview(&vw, pfp); fputc('\n', pfp);
676     if (frameNo > 0)
677     fprintf(pfp, "FRAME=%d\n", frameNo);
678     double pasp = viewaspect(&vw) * GetWidth() / GetHeight();
679     if (!FABSEQ(pasp, 1.0))
680     fputaspect(pasp, pfp);
681     fputnow(pfp);
682     switch (RDTcolorT(dt)) { // set primaries and picture format
683     case RDTrgbe:
684     if (!prims | (prims == xyzprims)) prims = stdprims;
685     fputprims(prims, pfp);
686     fputformat(COLRFMT, pfp);
687     break;
688     case RDTxyze:
689     prims = xyzprims;
690     fputformat(CIEFMT, pfp);
691     break;
692     case RDTscolr:
693     prims = NULL;
694     fputwlsplit(WLPART, pfp);
695     fputncomp(NCSAMP, pfp);
696     fputformat(SPECFMT, pfp);
697     break;
698     case RDTrgb:
699     if (!prims | (prims == xyzprims)) prims = stdprims;
700     fputprims(prims, pfp);
701     fputncomp(3, pfp);
702     fputendian(pfp);
703     fputformat("float", pfp);
704     break;
705     case RDTxyz:
706     prims = xyzprims;
707     fputprims(prims, pfp);
708     fputncomp(3, pfp);
709     fputendian(pfp);
710     fputformat("float", pfp);
711     break;
712     case RDTscolor:
713     prims = NULL;
714     fputwlsplit(WLPART, pfp);
715     fputncomp(NCSAMP, pfp);
716     fputendian(pfp);
717     fputformat("float", pfp);
718     break;
719     default:;
720     }
721     fputc('\n', pfp); // end picture header
722     fprtresolu(GetWidth(), GetHeight(), pfp);
723     if (dfname) {
724     if (dfname[0] == '!')
725     dfp = popen(dfname+1, "w");
726     else
727     dfp = fopen(dfname, "w");
728     if (!dfp) {
729     sprintf(errmsg, "cannot open depth output '%s'", dfname);
730     error(SYSTEM, errmsg);
731     return RDTnone;
732     }
733     SET_FILE_BINARY(dfp);
734     }
735     if (RDTdepthT(dt) == RDTdshort) { // write header for 16-bit depth?
736     newheader("RADIANCE", dfp);
737     fputs(GetHeader(), dfp);
738     fputs(VIEWSTR, dfp); fprintview(&vw, dfp); fputc('\n', dfp);
739     fputs(DEPTHSTR, dfp); fputs(dunit, dfp); fputc('\n', dfp);
740     fputformat(DEPTH16FMT, dfp);
741     fputc('\n', dfp); // end-of-info
742     fprtresolu(GetWidth(), GetHeight(), dfp);
743     }
744     const int bheight = (psample > 1) ? int(2*psample+.99) : 4;
745     const int vstep = bheight >> (psample > 1);
746    
747     NewBar(bheight); // render frame if we can
748     if (!RenderBelow(GetHeight(), vstep, pfp, dt, dfp)) {
749     fclose(pfp);
750     if (dfp) (dfname[0] == '!') ? pclose(dfp) : fclose(dfp);
751     Cleanup();
752     return RDTnone;
753     }
754     NewBar(); // clean up and return
755     if (pfp != stdout)
756     fclose(pfp);
757     if (dfp) {
758     if (dfname[0] == '!') {
759     int status = pclose(dfp);
760     if (status) {
761     sprintf(errmsg, "depth output (%s) error status: %d",
762     dfname, status);
763     error(USER, errmsg);
764     return RDTnone;
765     }
766     } else
767     fclose(dfp);
768     }
769     return dt;
770     }
771    
772     // passed struct for header line callback
773     struct HeaderInfo {
774     char fmt[MAXFMTLEN];
775     char depth_unit[32];
776     int ncomp;
777     RGBPRIMS prims;
778     VIEW vw;
779     bool gotWL;
780     bool gotprims;
781     bool gotview;
782     bool endianMatch;
783    
784     HeaderInfo() {
785     strcpy(fmt, "MISSING");
786     depth_unit[0] = '\0';
787     ncomp = 3;
788     vw = stdview;
789     gotWL = false;
790     gotprims = false;
791     gotview = false;
792     endianMatch = true;
793     }
794     };
795    
796     // helper function checks header line and records req. info.
797     static int
798     head_check(char *s, void *p)
799     {
800     HeaderInfo * hp = (HeaderInfo *)p;
801     int rval;
802    
803     if (isncomp(s)) {
804     hp->ncomp = ncompval(s);
805     return 1;
806     }
807     if (iswlsplit(s)) {
808     hp->gotWL = wlsplitval(WLPART, s);
809     return 1;
810     }
811     if (isprims(s)) {
812     hp->gotprims = primsval(hp->prims, s);
813     return 1;
814     }
815     if (isview(s)) {
816     hp->gotview |= (sscanview(&hp->vw, s) > 0);
817     return 1;
818     }
819     if (!strncmp(s, DEPTHSTR, LDEPTHSTR)) {
820     strlcpy(hp->depth_unit, s+LDEPTHSTR, sizeof(hp->depth_unit));
821     char * cp = hp->depth_unit;
822     while (*cp) cp++;
823     while (cp > hp->depth_unit && isspace(cp[-1])) cp--;
824     *cp = '\0';
825     return 1;
826     }
827     if ((rval = isbigendian(s)) >= 0) {
828     hp->endianMatch = (rval == nativebigendian());
829     return 1;
830     }
831     if (formatval(hp->fmt, s))
832     return 1;
833     return 0;
834     }
835    
836     // Resume partially finished rendering
837     // Picture file must exist
838     RenderDataType
839     RpictSimulManager::ResumeFrame(const char *pfname, const char *dfname)
840     {
841     if (!pfname || pfname[0] == '!')
842     return RDTnone;
843    
844     RenderDataType dt = RDTnone;
845     FILE * dfp = NULL;
846     FILE * pfp = fopen(pfname, "r+");
847     if (!pfp) {
848     sprintf(errmsg, "cannot reopen output picture '%s'", pfname);
849     error(SYSTEM, errmsg);
850     return RDTnone;
851     }
852     SET_FILE_BINARY(pfp);
853     HeaderInfo hinfo; // read header information & dimensions
854     RESOLU res;
855     if (getheader(pfp, head_check, &hinfo) < 0) {
856     fclose(pfp);
857     return RDTnone;
858     }
859     if (!fgetsresolu(&res, pfp) || res.rt != PIXSTANDARD) {
860     sprintf(errmsg, "missing/bad resolution for '%s'", pfname);
861     error(USER, errmsg);
862     fclose(pfp);
863     return RDTnone;
864     }
865     if (!hinfo.gotview) {
866     sprintf(errmsg, "missing view for '%s'", pfname);
867     error(USER, errmsg);
868     fclose(pfp);
869     return RDTnone;
870     }
871     if (hinfo.ncomp < 3) {
872     sprintf(errmsg, "bad # components (%d) in '%s'", hinfo.ncomp, pfname);
873     error(USER, errmsg);
874     fclose(pfp);
875     return RDTnone;
876     }
877     int bytesPer = 0; // complicated part to set rendering/output space
878     if (!strcmp(hinfo.fmt, COLRFMT)) {
879     prims = hinfo.prims;
880     int n = 8*hinfo.gotprims;
881     while (n--)
882     if (!FABSEQ(hinfo.prims[0][n], stdprims[0][n]))
883     break;
884     if (n < 0)
885     prims = stdprims;
886     dt = RDTnewCT(dt, RDTrgbe);
887     } else if (!strcmp(hinfo.fmt, CIEFMT)) {
888     prims = xyzprims;
889     dt = RDTnewCT(dt, RDTxyze);
890     } else if (!strcmp(hinfo.fmt, SPECFMT)) {
891     if ((hinfo.ncomp <= 3) | (hinfo.ncomp > MAXCSAMP)) {
892     sprintf(errmsg, "incompatible sample count (%d) in '%s'",
893     hinfo.ncomp, pfname);
894     error(USER, errmsg);
895     fclose(pfp);
896     return RDTnone;
897     }
898     NCSAMP = hinfo.ncomp; // overrides global setting
899     prims = NULL;
900     dt = RDTnewCT(dt, RDTscolr);
901     bytesPer = hinfo.ncomp + 1; // XXX assumes no compression
902     } else if (!strcmp(hinfo.fmt, "float")) {
903     if (!hinfo.endianMatch) {
904     sprintf(errmsg, "incompatible byte ordering in '%s'", pfname);
905     error(USER, errmsg);
906     fclose(pfp);
907     return RDTnone;
908     }
909     if (hinfo.ncomp == 3) {
910     prims = hinfo.prims; // custom primaries?
911     int n = 8*hinfo.gotprims;
912     while (n--)
913     if (!FABSEQ(hinfo.prims[0][n], stdprims[0][n]))
914     break;
915     if (n < 0) // standard primaries?
916     prims = stdprims;
917     else if (hinfo.gotprims) { // or check if XYZ
918     for (n = 8; n--; )
919     if (!FABSEQ(prims[0][n], xyzprims[0][n]))
920     break;
921     if (n < 0)
922     prims = xyzprims;
923     }
924     if (prims == xyzprims)
925     dt = RDTnewCT(dt, RDTxyz);
926     else
927     dt = RDTnewCT(dt, RDTrgb);
928     } else {
929     NCSAMP = hinfo.ncomp; // overrides global setting
930     prims = NULL;
931     dt = RDTnewCT(dt, RDTscolor);
932     }
933     bytesPer = sizeof(float)*hinfo.ncomp;
934     } else {
935     sprintf(errmsg, "unknown format (%s) for '%s'", hinfo.fmt, pfname);
936     error(USER, errmsg);
937     fclose(pfp);
938     return RDTnone;
939     }
940     vw.type = 0; // set up new (unreferenced) frame
941     frameNo = 0;
942     int hvdim[2] = {res.xr, res.yr};
943     double noAdj = 0;
944     if (!NewFrame(hinfo.vw, hvdim, &noAdj) ||
945     (hvdim[0] != res.xr) | (hvdim[1] != res.yr)) {
946     fclose(pfp);
947     return RDTnone;
948     }
949     long dataStart = ftell(pfp); // picture starting point
950     if (dataStart < 0) {
951     sprintf(errmsg, "cannot seek on '%s'", pfname);
952     error(SYSTEM, errmsg);
953     fclose(pfp);
954     return RDTnone;
955     }
956     long doneScans = 0;
957     if (bytesPer) { // fixed-width records?
958     fseek(pfp, 0, SEEK_END);
959     long dataEnd = ftell(pfp);
960     doneScans = (dataEnd - dataStart)/(bytesPer*GetWidth());
961     if (dataEnd-dataStart > bytesPer*GetWidth()*doneScans)
962     fseek(pfp, dataStart + bytesPer*GetWidth()*doneScans, SEEK_SET);
963     } else { // else get compressed scanlines
964     COLR * scan = (COLR *)tempbuffer(sizeof(COLR)*GetWidth());
965     while (freadcolrs(scan, GetWidth(), pfp) >= 0)
966     ++doneScans;
967     if (!feof(pfp)) {
968     sprintf(errmsg, "error reading compressed scanline from '%s'", pfname);
969     error(USER, errmsg);
970     fclose(pfp);
971     return RDTnone;
972     }
973     }
974     if (doneScans >= GetHeight()) { // nothing left to do?
975     sprintf(errmsg, "output file '%s' is already complete", pfname);
976     error(WARNING, errmsg);
977     fclose(pfp);
978     return dt;
979     }
980     if (!doneScans) {
981     sprintf(errmsg, "restarting empty frame '%s'", pfname);
982     error(WARNING, errmsg);
983     }
984     if (dfname) { // append depth file, too?
985     if (dfname[0] == '!') {
986     error(USER, "depth data cannot be reloaded from command");
987     fclose(pfp);
988     return RDTnone;
989     }
990     dfp = fopen(dfname, "a");
991     if (!dfp) {
992     sprintf(errmsg, "cannot reopen depth file '%s'", dfname);
993     error(SYSTEM, errmsg);
994     fclose(pfp);
995     return RDTnone;
996     }
997     SET_FILE_BINARY(dfp);
998     const long dflen = ftell(dfp);
999     if (dflen != sizeof(float)*GetWidth()*doneScans) {
1000     fclose(dfp);
1001     dfp = fopen(dfname, "r+");
1002     if (!dfp) return RDTnone; // WTH?
1003     SET_FILE_BINARY(dfp);
1004     }
1005     if (dflen < sizeof(float)*GetWidth()*doneScans) {
1006     HeaderInfo dinfo;
1007     if (getheader(dfp, head_check, &dinfo) < 0)
1008     sprintf(errmsg, "bad header in encoded depth file '%s'",
1009     dfname);
1010     else if (strcmp(dinfo.fmt, DEPTH16FMT))
1011     sprintf(errmsg, "wrong format (%s) for depth file '%s'",
1012     dinfo.fmt, dfname);
1013     else if (!SetReferenceDepth(dinfo.depth_unit))
1014     sprintf(errmsg, "bad/missing reference depth (%s) in '%s'",
1015     dinfo.depth_unit, dfname);
1016     else if (!fscnresolu(hvdim, hvdim+1, dfp) ||
1017     (hvdim[0] != GetWidth()) | (hvdim[1] != GetHeight()))
1018     sprintf(errmsg, "bad/mismatched resolution in '%s'",
1019     dfname);
1020     else
1021     errmsg[0] = '\0';
1022    
1023     if (errmsg[0]) {
1024     error(USER, errmsg);
1025     fclose(dfp);
1026     fclose(pfp);
1027     return RDTnone;
1028     }
1029     const long dStart = ftell(dfp);
1030     if (dflen-dStart < 2*GetWidth()*doneScans) {
1031     sprintf(errmsg, "missing %ld depths in '%s'",
1032     (long)GetWidth()*doneScans - (dflen-dStart)/2,
1033     dfname);
1034     error(WARNING, errmsg);
1035     }
1036     fseek(dfp, dStart + 2*GetWidth()*doneScans, SEEK_SET);
1037     dt = RDTnewDT(dt, RDTdshort);
1038     } else {
1039     if (dflen > sizeof(float)*GetWidth()*doneScans)
1040     fseek(dfp, sizeof(float)*GetWidth()*doneScans, SEEK_SET);
1041     dt = RDTnewDT(dt, RDTdfloat);
1042     }
1043     }
1044     int bheight = (psample > 1) ? int(2*psample+.99) : 4;
1045     if (bheight > GetHeight()-doneScans)
1046     bheight = GetHeight()-doneScans;
1047     int vstep = bheight >> (psample > 1);
1048     vstep += !vstep;
1049    
1050     NewBar(bheight); // render remainder if we can
1051     if (!RenderBelow(GetHeight()-doneScans, vstep, pfp, dt, dfp)) {
1052     fclose(pfp);
1053     if (dfp) fclose(dfp);
1054     Cleanup();
1055     return RDTnone;
1056     }
1057     NewBar(); // close up and return success
1058     fclose(pfp);
1059     if (dfp) fclose(dfp);
1060     return dt;
1061     }