1 /***************************************************************************
2 * Copyright (C) 1998-2008 by authors (see AUTHORS.txt ) *
4 * This file is part of LuxRender. *
6 * Lux Renderer is free software; you can redistribute it and/or modify *
7 * it under the terms of the GNU General Public License as published by *
8 * the Free Software Foundation; either version 3 of the License, or *
9 * (at your option) any later version. *
11 * Lux Renderer is distributed in the hope that it will be useful, *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 * GNU General Public License for more details. *
16 * You should have received a copy of the GNU General Public License *
17 * along with this program. If not, see <http://www.gnu.org/licenses/>. *
19 * This project is based on PBRT ; see http://www.pbrt.org *
20 * Lux Renderer website : http://www.luxrender.net *
21 ***************************************************************************/
25 #include "randomgen.h"
29 #include "cameraresponse.h"
31 #include "contribution.h"
32 #include "blackbodyspd.h"
39 #include <boost/archive/text_oarchive.hpp>
40 #include <boost/archive/text_iarchive.hpp>
41 #include <boost/archive/binary_oarchive.hpp>
42 #include <boost/archive/binary_iarchive.hpp>
43 #include <boost/serialization/split_member.hpp>
44 #include <boost/iostreams/filtering_stream.hpp>
45 #include <boost/iostreams/filtering_streambuf.hpp>
46 #include <boost/iostreams/copy.hpp>
47 #include <boost/iostreams/filter/zlib.hpp>
48 #include <boost/iostreams/filter/bzip2.hpp>
49 #include <boost/iostreams/filter/gzip.hpp>
50 #include <boost/filesystem.hpp>
51 #include <boost/thread.hpp>
53 #define cimg_display_type 0
55 #ifdef LUX_USE_CONFIG_H
59 #define cimg_use_png 1
63 #define cimg_use_jpeg 1
67 #define cimg_use_tiff 1
71 #else //LUX_USE_CONFIG_H
72 #define cimg_use_png 1
73 #define cimg_use_tiff 1
74 #define cimg_use_jpeg 1
75 #endif //LUX_USE_CONFIG_H
77 #define cimg_debug 0 // Disable modal window in CImg exceptions.
78 // Include the CImg Library, with the GREYCstoration plugin included
79 #define cimg_plugin "greycstoration.h"
82 using namespace cimg_library;
87 using namespace boost::iostreams;
92 static T bilinearSampleImage(const vector<T> &pixels,
93 const u_int xResolution, const u_int yResolution,
94 const float x, const float y)
96 u_int x1 = Clamp(Floor2UInt(x), 0U, xResolution - 1);
97 u_int y1 = Clamp(Floor2UInt(y), 0U, yResolution - 1);
98 u_int x2 = Clamp(x1 + 1, 0U, xResolution - 1);
99 u_int y2 = Clamp(y1 + 1, 0U, yResolution - 1);
100 float tx = Clamp(x - static_cast<float>(x1), 0.f, 1.f);
101 float ty = Clamp(y - static_cast<float>(y1), 0.f, 1.f);
104 c.AddWeighted((1.f - tx) * (1.f - ty), pixels[y1 * xResolution + x1]);
105 c.AddWeighted(tx * (1.f - ty), pixels[y1 * xResolution + x2]);
106 c.AddWeighted((1.f - tx) * ty, pixels[y2 * xResolution + x1]);
107 c.AddWeighted(tx * ty, pixels[y2 * xResolution + x2]);
113 static void horizontalGaussianBlur(const vector<XYZColor> &in, vector<XYZColor> &out,
114 const u_int xResolution, const u_int yResolution, float std_dev)
116 u_int rad_needed = Ceil2UInt(std_dev * 4.f);//kernel_radius;
118 const u_int lookup_size = rad_needed + 1;
119 //build filter lookup table
120 std::vector<float> filter_weights(lookup_size);
122 for(u_int x = 0; x < lookup_size; ++x) {
123 filter_weights[x] = expf(-static_cast<float>(x) * static_cast<float>(x) / (std_dev * std_dev));
125 if (filter_weights[x] < 1e-12f) {
129 sweight += 2.f * filter_weights[x];
131 sweight += filter_weights[x];
134 //normalise filter kernel
135 sweight = 1.f / sweight;
137 const u_int pixel_rad = rad_needed;
139 //------------------------------------------------------------------
140 //blur in x direction
141 //------------------------------------------------------------------
142 for(u_int y = 0; y < yResolution; ++y) {
143 for(u_int x = 0; x < xResolution; ++x) {
144 const u_int a = y * xResolution + x;
146 out[a] = XYZColor(0.f);
148 for (u_int i = max(x, pixel_rad) - pixel_rad; i <= min(x + pixel_rad, xResolution - 1); ++i) {
150 out[a].AddWeighted(filter_weights[x - i], in[a + i - x]);
152 out[a].AddWeighted(filter_weights[i - x], in[a + i - x]);
158 static void rotateImage(const vector<XYZColor> &in, vector<XYZColor> &out,
159 const u_int xResolution, const u_int yResolution, float angle)
161 const u_int maxRes = max(xResolution, yResolution);
163 const float s = sinf(-angle);
164 const float c = cosf(-angle);
166 const float cx = xResolution * 0.5f;
167 const float cy = yResolution * 0.5f;
169 for(u_int y = 0; y < maxRes; ++y) {
170 float px = 0.f - maxRes * 0.5f;
171 float py = y - maxRes * 0.5f;
173 float rx = px * c - py * s + cx;
174 float ry = px * s + py * c + cy;
175 for(u_int x = 0; x < maxRes; ++x) {
176 out[y*maxRes + x] = bilinearSampleImage<XYZColor>(in, xResolution, yResolution, rx, ry);
186 // Image Pipeline Function Definitions
187 void ApplyImagingPipeline(vector<XYZColor> &xyzpixels,
188 u_int xResolution, u_int yResolution,
189 const GREYCStorationParams &GREYCParams, const ChiuParams &chiuParams,
190 const ColorSystem &colorSpace, Histogram *histogram, bool HistogramEnabled,
191 bool &haveBloomImage, XYZColor *&bloomImage, bool bloomUpdate,
192 float bloomRadius, float bloomWeight,
193 bool VignettingEnabled, float VignetScale,
194 bool aberrationEnabled, float aberrationAmount,
195 bool &haveGlareImage, XYZColor *&glareImage, bool glareUpdate,
196 float glareAmount, float glareRadius, u_int glareBlades, float glareThreshold,
197 const char *toneMapName, const ParamSet *toneMapParams,
198 const CameraResponse *response, float dither)
200 const u_int nPix = xResolution * yResolution;
203 for (u_int i = 0; i < nPix; ++i)
204 xyzpixels[i] = xyzpixels[i].Clamp();
207 // Possibly apply bloom effect to image
208 if (bloomRadius > 0.f && bloomWeight > 0.f) {
210 // Compute image-space extent of bloom effect
211 const u_int bloomSupport = Float2UInt(bloomRadius *
212 max(xResolution, yResolution));
213 const u_int bloomWidth = bloomSupport / 2;
214 // Initialize bloom filter table
215 vector<float> bloomFilter(bloomWidth * bloomWidth);
216 for (u_int i = 0; i < bloomWidth * bloomWidth; ++i) {
217 float dist = sqrtf(i) / bloomWidth;
218 bloomFilter[i] = powf(max(0.f, 1.f - dist), 4.f);
221 // Allocate persisting bloom image layer if unallocated
222 if(!haveBloomImage) {
223 bloomImage = new XYZColor[nPix];
224 haveBloomImage = true;
227 // Apply bloom filter to image pixels
228 // vector<Color> bloomImage(nPix);
229 // ProgressReporter prog(yResolution, "Bloom filter"); //NOBOOK //intermediate crashfix until imagepipelinerefactor is done - Jens
230 for (u_int y = 0; y < yResolution; ++y) {
231 for (u_int x = 0; x < xResolution; ++x) {
232 // Compute bloom for pixel _(x,y)_
233 // Compute extent of pixels contributing bloom
234 const u_int x0 = max(x, bloomWidth) - bloomWidth;
235 const u_int x1 = min(x + bloomWidth, xResolution - 1);
236 const u_int y0 = max(y, bloomWidth) - bloomWidth;
237 const u_int y1 = min(y + bloomWidth, yResolution - 1);
238 const u_int offset = y * xResolution + x;
240 for (u_int by = y0; by <= y1; ++by) {
241 for (u_int bx = x0; bx <= x1; ++bx) {
242 if (bx == x && by == y)
244 // Accumulate bloom from pixel $(bx,by)$
245 const u_int dist2 = (x - bx) * (x - bx) + (y - by) * (y - by);
246 if (dist2 < bloomWidth * bloomWidth) {
247 u_int bloomOffset = bx + by * xResolution;
248 float wt = bloomFilter[dist2];
250 bloomImage[offset].AddWeighted(wt, xyzpixels[bloomOffset]);
254 bloomImage[offset] /= sumWt;
256 // prog.Update(); //NOBOOK //intermediate crashfix until imagepipelinerefactor is done - Jens
258 // prog.Done(); //NOBOOK //intermediate crashfix until imagepipelinerefactor is done - Jens
261 // Mix bloom effect into each pixel
262 if(haveBloomImage && bloomImage != NULL)
263 for (u_int i = 0; i < nPix; ++i)
264 xyzpixels[i] = Lerp(bloomWeight, xyzpixels[i], bloomImage[i]);
267 if (glareRadius > 0.f && glareAmount > 0.f) {
269 // Allocate persisting glare image layer if unallocated
270 if(!haveGlareImage) {
271 glareImage = new XYZColor[nPix];
272 haveGlareImage = true;
275 u_int maxRes = max(xResolution, yResolution);
276 u_int nPix2 = maxRes * maxRes;
278 std::vector<XYZColor> rotatedImage(nPix2);
279 std::vector<XYZColor> blurredImage(nPix2);
280 std::vector<XYZColor> darkenedImage(nPix);
281 for(u_int i = 0; i < nPix; ++i)
282 glareImage[i] = XYZColor(0.f);
284 // Search for the brightest pixel in the image
286 for(u_int i = 1; i < nPix; ++i) {
287 if (xyzpixels[i].c[1] > xyzpixels[max].c[1])
291 // glareThreshold ranges between 0-1,
292 // but this relative value has to be converted to
293 //an absolute value fitting the image being processed
294 float glareAbsoluteThreshold = xyzpixels[max].c[1] *
296 // Every pixel that is not bright enough is made black
297 for(u_int i = 0; i < nPix; ++i) {
298 if(xyzpixels[i].c[1] < glareAbsoluteThreshold)
299 darkenedImage[i] = XYZColor(0.f);
301 darkenedImage[i] = xyzpixels[i];
304 const float radius = maxRes * glareRadius;
306 // add rotated versions of the blurred image
307 const float invBlades = 1.f / glareBlades;
309 for (u_int i = 0; i < glareBlades; ++i) {
310 rotateImage(darkenedImage, rotatedImage, xResolution, yResolution, angle);
311 horizontalGaussianBlur(rotatedImage, blurredImage, maxRes, maxRes, radius);
312 rotateImage(blurredImage, rotatedImage, maxRes, maxRes, -angle);
315 for(u_int y = 0; y < yResolution; ++y) {
316 for(u_int x = 0; x < xResolution; ++x) {
317 const u_int sx = x + (maxRes - xResolution) / 2;
318 const u_int sy = y + (maxRes - yResolution) / 2;
320 glareImage[y * xResolution + x] += rotatedImage[sy * maxRes + sx];
323 angle += 2.f * M_PI * invBlades;
327 for(u_int i = 0; i < nPix; ++i)
328 glareImage[i] *= invBlades;
330 rotatedImage.clear();
331 blurredImage.clear();
332 darkenedImage.clear();
335 if (haveGlareImage && glareImage != NULL) {
336 for(u_int i = 0; i < nPix; ++i) {
337 xyzpixels[i] += glareAmount * glareImage[i];
342 // Apply tone reproduction to image
344 ToneMap *toneMap = MakeToneMap(toneMapName,
345 toneMapParams ? *toneMapParams : ParamSet());
347 toneMap->Map(xyzpixels, xResolution, yResolution, 100.f);
352 vector<RGBColor> &rgbpixels = reinterpret_cast<vector<RGBColor> &>(xyzpixels);
353 for (u_int i = 0; i < nPix; ++i)
354 rgbpixels[i] = colorSpace.ToRGBConstrained(xyzpixels[i]);
356 // DO NOT USE xyzpixels ANYMORE AFTER THIS POINT
357 if (response && response->validFile) {
358 for (u_int i = 0; i < nPix; ++i)
359 response->Map(rgbpixels[i]);
362 // Add vignetting & chromatic aberration effect
363 // These are paired in 1 loop as they can share quite a few calculations
364 if ((VignettingEnabled && VignetScale != 0.0f) ||
365 (aberrationEnabled && aberrationAmount > 0.f)) {
367 RGBColor *outp = &rgbpixels[0];
368 std::vector<RGBColor> aberrationImage;
369 if (aberrationEnabled) {
370 aberrationImage.resize(nPix, RGBColor(0.f));
371 outp = &aberrationImage[0];
374 const float invxRes = 1.f / xResolution;
375 const float invyRes = 1.f / yResolution;
376 //for each pixel in the source image
377 for(u_int y = 0; y < yResolution; ++y) {
378 for(u_int x = 0; x < xResolution; ++x) {
379 const float nPx = x * invxRes;
380 const float nPy = y * invyRes;
381 const float xOffset = nPx - 0.5f;
382 const float yOffset = nPy - 0.5f;
383 const float tOffset = sqrtf(xOffset * xOffset + yOffset * yOffset);
385 if (aberrationEnabled && aberrationAmount > 0.f) {
386 const float rb_x = (0.5f + xOffset * (1.f + tOffset * aberrationAmount)) * xResolution;
387 const float rb_y = (0.5f + yOffset * (1.f + tOffset * aberrationAmount)) * yResolution;
388 const float g_x = (0.5f + xOffset * (1.f - tOffset * aberrationAmount)) * xResolution;
389 const float g_y = (0.5f + yOffset * (1.f - tOffset * aberrationAmount)) * yResolution;
391 const float redblue[] = {1.f, 0.f, 1.f};
392 const float green[] = {0.f, 1.f, 0.f};
394 outp[xResolution * y + x] += RGBColor(redblue) * bilinearSampleImage<RGBColor>(rgbpixels, xResolution, yResolution, rb_x, rb_y);
395 outp[xResolution * y + x] += RGBColor(green) * bilinearSampleImage<RGBColor>(rgbpixels, xResolution, yResolution, g_x, g_y);
399 if(VignettingEnabled && VignetScale != 0.0f) {
400 // normalize to range [0.f - 1.f]
401 const float invNtOffset = 1.f - (fabsf(tOffset) * 1.42f);
402 float vWeight = Lerp(invNtOffset, 1.f - VignetScale, 1.f);
403 for (u_int i = 0; i < 3; ++i)
404 outp[xResolution*y + x].c[i] *= vWeight;
409 if (aberrationEnabled) {
410 for(u_int i = 0; i < nPix; ++i)
411 rgbpixels[i] = aberrationImage[i];
414 aberrationImage.clear();
417 // Calculate histogram (if it is enabled and exists)
418 if (HistogramEnabled && histogram)
419 histogram->Calculate(rgbpixels, xResolution, yResolution);
421 // Apply Chiu Noise Reduction Filter
422 if(chiuParams.enabled) {
423 std::vector<RGBColor> chiuImage(nPix, RGBColor(0.f));
425 // NOTE - lordcrc - if includecenter is false, make sure radius
426 // is a tad higher than 1 to include other pixels
427 const float radius = max(chiuParams.radius, 1.f + (chiuParams.includecenter ? 0.f : 1e-6f));
429 const u_int pixel_rad = Ceil2UInt(radius);
430 const u_int lookup_size = 2 * pixel_rad + 1;
432 //build filter lookup table
433 std::vector<float> weights(lookup_size * lookup_size);
435 float sumweight = 0.f;
437 for(int y = -static_cast<int>(pixel_rad); y <= static_cast<int>(pixel_rad); ++y) {
438 for(int x = -static_cast<int>(pixel_rad); x <= static_cast<int>(pixel_rad); ++x) {
440 weights[offset] = chiuParams.includecenter ? 1.0f : 0.0f;
444 const float dist = sqrtf(dx * dx + dy * dy);
445 const float weight = powf(max(0.0f, 1.0f - dist / radius), 4.0f);
446 weights[offset] = weight;
448 sumweight += weights[offset++];
452 //normalise filter kernel
453 for(u_int y = 0; y < lookup_size; ++y)
454 for(u_int x = 0; x < lookup_size; ++x)
455 weights[lookup_size*y + x] /= sumweight;
457 //for each pixel in the source image
458 for (u_int y = 0; y < yResolution; ++y) {
459 //get min and max of current filter rect along y axis
460 const u_int miny = max(y, pixel_rad) - pixel_rad;
461 const u_int maxy = min(yResolution - 1, y + pixel_rad);
463 for (u_int x = 0; x < xResolution; ++x) {
464 //get min and max of current filter rect along x axis
465 const u_int minx = max(x, pixel_rad) - pixel_rad;
466 const u_int maxx = min(xResolution - 1, x + pixel_rad);
468 // For each pixel in the out image, in the filter radius
469 for(u_int ty = miny; ty < maxy; ++ty) {
470 for(u_int tx = minx; tx < maxx; ++tx) {
471 const u_int dx = x - tx + pixel_rad;
472 const u_int dy = y - ty + pixel_rad;
473 const float factor = weights[lookup_size*dy + dx];
474 chiuImage[xResolution*ty + tx].AddWeighted(factor, rgbpixels[xResolution*y + x]);
480 for(u_int i = 0; i < nPix; ++i)
481 rgbpixels[i] = chiuImage[i];
483 // remove used intermediate memory
488 // Apply GREYCStoration noise reduction filter
489 if(GREYCParams.enabled) {
490 // Define Cimg image buffer and copy data
491 CImg<float> img(xResolution, yResolution, 1, 3);
492 for(u_int y = 0; y < yResolution; ++y) {
493 for(u_int x = 0; x < xResolution; ++x) {
494 const u_int index = xResolution * y + x;
495 // Note - Cimg float data must be in range [0,255] for GREYCStoration to work %100
496 for(u_int j = 0; j < 3; ++j)
497 img(x, y, 0, j) = rgbpixels[index].c[j] * 255;
501 for (unsigned int iter=0; iter<GREYCParams.nb_iter; iter++) {
502 img.blur_anisotropic(GREYCParams.amplitude,
503 GREYCParams.sharpness,
504 GREYCParams.anisotropy,
509 GREYCParams.gauss_prec,
511 GREYCParams.fast_approx,
515 // Copy data from cimg buffer back to pixels vector
516 const float inv_byte = 1.f/255;
517 for(u_int y = 0; y < yResolution; ++y) {
518 for(u_int x = 0; x < xResolution; ++x) {
519 const u_int index = xResolution * y + x;
520 for(u_int j = 0; j < 3; ++j)
521 rgbpixels[index].c[j] = img(x, y, 0, j) * inv_byte;
528 for (u_int i = 0; i < nPix; ++i)
529 rgbpixels[i] += 2.f * dither * (lux::random::floatValueP() - .5f);
533 // Filter Look Up Table Definitions
535 FilterLUT::FilterLUT(Filter *filter, const float offsetX, const float offsetY) {
536 const int x0 = Ceil2Int(offsetX - filter->xWidth);
537 const int x1 = Floor2Int(offsetX + filter->xWidth);
538 const int y0 = Ceil2Int(offsetY - filter->yWidth);
539 const int y1 = Floor2Int(offsetY + filter->yWidth);
540 lutWidth = x1 - x0 + 1;
541 lutHeight = y1 - y0 + 1;
542 //lut = new float[lutWidth * lutHeight];
543 lut.resize(lutWidth * lutHeight);
545 float totalWeight = 0.f;
546 unsigned int index = 0;
547 for (int iy = y0; iy <= y1; ++iy) {
548 for (int ix = x0; ix <= x1; ++ix) {
549 const float filterVal = filter->Evaluate(fabsf(ix - offsetX), fabsf(iy - offsetY));
550 totalWeight += filterVal;
551 lut[index++] = filterVal;
557 for (int iy = y0; iy <= y1; ++iy) {
558 for (int ix = x0; ix <= x1; ++ix)
559 lut[index++] /= totalWeight;
563 FilterLUTs::FilterLUTs(Filter *filter, const unsigned int size) {
565 step = 1.f / float(size);
567 luts.resize(lutsSize * lutsSize);
569 for (unsigned int iy = 0; iy < lutsSize; ++iy) {
570 for (unsigned int ix = 0; ix < lutsSize; ++ix) {
571 const float x = ix * step - 0.5f + step / 2.f;
572 const float y = iy * step - 0.5f + step / 2.f;
574 luts[ix + iy * lutsSize] = FilterLUT(filter, x, y);
579 // OutlierData Definitions
580 ColorSystem OutlierData::cs(0.63f, 0.34f, 0.31f, 0.595f, 0.155f, 0.07f, 0.314275f, 0.329411f);
582 // Film Function Definitions
584 u_int Film::GetXResolution()
589 u_int Film::GetYResolution()
594 Film::Film(u_int xres, u_int yres, Filter *filt, u_int filtRes, const float crop[4],
595 const string &filename1, bool premult, bool useZbuffer,
596 bool w_resume_FLM, bool restart_resume_FLM, bool write_FLM_direct, int haltspp, int halttime,
597 bool debugmode, int outlierk, int tilec) :
599 xResolution(xres), yResolution(yres),
600 EV(0.f), averageLuminance(0.f),
601 numberOfSamplesFromNetwork(0), numberOfLocalSamples(0), numberOfResumedSamples(0),
602 contribPool(NULL), filter(filt), filterTable(NULL), filterLUTs(NULL),
604 colorSpace(0.63f, 0.34f, 0.31f, 0.595f, 0.155f, 0.07f, 0.314275f, 0.329411f), // default is SMPTE
605 ZBuffer(NULL), use_Zbuf(useZbuffer),
606 debug_mode(debugmode), premultiplyAlpha(premult),
607 writeResumeFlm(w_resume_FLM), restartResumeFlm(restart_resume_FLM), writeFlmDirect(write_FLM_direct),
608 outlierRejection_k(outlierk), haltSamplesPerPixel(haltspp),
609 haltTime(halttime), histogram(NULL), enoughSamplesPerPixel(false)
611 // Compute film image extent
612 memcpy(cropWindow, crop, 4 * sizeof(float));
613 xPixelStart = Ceil2UInt(xResolution * cropWindow[0]);
614 xPixelCount = max(1U, Ceil2UInt(xResolution * cropWindow[1]) - xPixelStart);
615 yPixelStart = Ceil2UInt(yResolution * cropWindow[2]);
616 yPixelCount = max(1U, Ceil2UInt(yResolution * cropWindow[3]) - yPixelStart);
617 int xRealWidth = Floor2Int(xPixelStart + .5f + xPixelCount + filter->xWidth) - Floor2Int(xPixelStart + .5f - filter->xWidth);
618 int yRealHeight = Floor2Int(yPixelStart + .5f + yPixelCount + filter->yWidth) - Floor2Int(yPixelStart + .5f - filter->yWidth);
619 samplePerPass = xRealWidth * yRealHeight;
621 boost::xtime_get(&creationTime, boost::TIME_UTC);
623 //Queryable parameters
624 AddIntAttribute(*this, "xResolution", "Horizontal resolution (pixels)", &Film::GetXResolution);
625 AddIntAttribute(*this, "yResolution", "Vertical resolution (pixels)", &Film::GetYResolution);
626 AddBoolAttribute(*this, "premultiplyAlpha", "Premultiplied alpha enabled", &Film::premultiplyAlpha);
627 AddStringAttribute(*this, "filename", "Output filename", filename, &Film::filename, Queryable::ReadWriteAccess);
628 AddFloatAttribute(*this, "EV", "Exposure value", &Film::EV);
629 AddFloatAttribute(*this, "averageLuminance", "Average Image Luminance", &Film::averageLuminance);
630 AddDoubleAttribute(*this, "numberOfLocalSamples", "Number of samples contributed to film on the local machine", &Film::numberOfLocalSamples);
631 AddDoubleAttribute(*this, "numberOfSamplesFromNetwork", "Number of samples contributed from network slaves", &Film::numberOfSamplesFromNetwork);
632 AddDoubleAttribute(*this, "numberOfResumedSamples", "Number of samples loaded from saved film", &Film::numberOfResumedSamples);
633 AddBoolAttribute(*this, "enoughSamples", "Indicates if the halt condition been reached", &Film::enoughSamplesPerPixel);
634 AddIntAttribute(*this, "haltSamplesPerPixel", "Halt Samples per Pixel", haltSamplesPerPixel, &Film::haltSamplesPerPixel, Queryable::ReadWriteAccess);
635 AddIntAttribute(*this, "haltTime", "Halt time in seconds", haltTime, &Film::haltTime, Queryable::ReadWriteAccess);
636 AddBoolAttribute(*this, "writeResumeFlm", "Write resume file", writeResumeFlm, &Film::writeResumeFlm, Queryable::ReadWriteAccess);
637 AddBoolAttribute(*this, "restartResumeFlm", "Restart (overwrite) resume file", restartResumeFlm, &Film::restartResumeFlm, Queryable::ReadWriteAccess);
638 AddBoolAttribute(*this, "writeFlmDirect", "Write resume file directly to disk", writeFlmDirect, &Film::writeFlmDirect, Queryable::ReadWriteAccess);
640 // Precompute filter tables
641 filterLUTs = new FilterLUTs(filt, max(min(filtRes, 64u), 2u));
643 outlierCellWidth = Floor2UInt(2 * filter->xWidth);
644 outlierInvCellWidth = 1.f / outlierCellWidth;
645 outlierCellHeight = Floor2UInt(2 * filter->yWidth);
646 outlierInvCellHeight = 1.f / outlierCellHeight;
648 const u_int thread_count = boost::thread::hardware_concurrency();
649 // base min tile size on outlier cell height, as it's a good measure anyway
650 const u_int minTileHeight = outlierCellHeight;
654 // TODO - thread_count * 2 is fairly arbitrary, find better choice?
655 tileCount = thread_count * (tilec < 0 ? -tilec : 2);
657 LOG(LUX_DEBUG, LUX_NOERROR) << "Requested film tile count: " << tileCount;
659 tileCount = Clamp(tileCount, 1u, yRealHeight / minTileHeight);
662 tileHeight = max(Ceil2UInt(static_cast<float>(yRealHeight) / tileCount), minTileHeight);
663 if (outlierRejection_k > 0) {
664 // if outlier rejection is enabled, tileHeight must be multiple of outlierCellHeight
665 // increase tileHeight to ensure this
666 tileHeight = outlierCellHeight * max(Ceil2UInt(static_cast<float>(tileHeight) / outlierCellHeight), 1u);
667 tileCount = max(Ceil2UInt(static_cast<float>(yRealHeight) / tileHeight), 1u);
670 LOG(LUX_DEBUG, LUX_NOERROR) << "Actual film tile count: " << tileCount;
672 invTileHeight = 1.f / tileHeight;
673 tileOffset = -0.5f - filter->yWidth - yPixelStart;
674 tileOffset2 = 2 * filter->yWidth * invTileHeight;
676 if (outlierRejection_k > 0) {
677 const u_int outliers_width = xRealWidth / outlierCellWidth;
678 const u_int outliers_height = yRealHeight / outlierCellHeight;
679 outliers.resize(outliers_height);
680 for (size_t i = 0; i < outliers.size(); ++i)
681 outliers[i].resize(outliers_width);
682 // tiles need duplicate data for row above and below tile
683 tileborder_outliers.resize(2 * tileCount);
684 for (size_t i = 0; i < tileborder_outliers.size(); ++i)
685 tileborder_outliers[i].resize(outliers_width);
698 void Film::RequestBufferGroups(const vector<string> &bg)
700 for (u_int i = 0; i < bg.size(); ++i)
701 bufferGroups.push_back(BufferGroup(bg[i]));
704 u_int Film::RequestBuffer(BufferType type, BufferOutputConfig output,
705 const string& filePostfix)
707 bufferConfigs.push_back(BufferConfig(type, output, filePostfix));
708 return bufferConfigs.size() - 1;
711 void Film::CreateBuffers()
713 if (bufferGroups.size() == 0)
714 bufferGroups.push_back(BufferGroup("default"));
715 for (u_int i = 0; i < bufferGroups.size(); ++i)
716 bufferGroups[i].CreateBuffers(bufferConfigs,xPixelCount,yPixelCount);
718 // Allocate ZBuf buffer if needed
720 ZBuffer = new PerPixelNormalizedFloatBuffer(xPixelCount,yPixelCount);
722 // Dade - check if we have to resume a rendering and restore the buffers
724 const string fname = filename+".flm";
725 if (restartResumeFlm) {
726 const string oldfname = fname + "1";
727 if (boost::filesystem::exists(fname)) {
728 if (boost::filesystem::exists(oldfname))
729 remove(oldfname.c_str());
730 rename(fname.c_str(), oldfname.c_str());
733 // Dade - check if the film file exists
734 std::ifstream ifs(fname.c_str(), std::ios_base::in | std::ios_base::binary);
737 // Dade - read the data
738 LOG(LUX_INFO,LUX_NOERROR)<< "Reading film status from file " << fname;
739 numberOfResumedSamples = UpdateFilm(ifs);
746 // initialize the contribution pool
747 contribPool = new ContributionPool(this);
750 void Film::ClearBuffers()
752 for (u_int i = 0; i < bufferGroups.size(); ++i) {
754 BufferGroup& bufferGroup = bufferGroups[i];
756 for (u_int j = 0; j < bufferConfigs.size(); ++j) {
757 Buffer* buffer = bufferGroup.getBuffer(j);
762 bufferGroup.numberOfSamples = 0;
766 void Film::SetGroupName(u_int index, const string& name)
768 if( index >= bufferGroups.size())
770 bufferGroups[index].name = name;
772 string Film::GetGroupName(u_int index) const
774 if (index >= bufferGroups.size())
776 return bufferGroups[index].name;
778 void Film::SetGroupEnable(u_int index, bool status)
780 if (index >= bufferGroups.size())
782 bufferGroups[index].enable = status;
784 bool Film::GetGroupEnable(u_int index) const
786 if (index >= bufferGroups.size())
788 return bufferGroups[index].enable;
790 void Film::SetGroupScale(u_int index, float value)
792 if (index >= bufferGroups.size())
794 bufferGroups[index].globalScale = value;
795 ComputeGroupScale(index);
797 float Film::GetGroupScale(u_int index) const
799 if (index >= bufferGroups.size())
801 return bufferGroups[index].globalScale;
803 void Film::SetGroupRGBScale(u_int index, const RGBColor &value)
805 if (index >= bufferGroups.size())
807 bufferGroups[index].rgbScale = value;
808 ComputeGroupScale(index);
810 RGBColor Film::GetGroupRGBScale(u_int index) const
812 if (index >= bufferGroups.size())
814 return bufferGroups[index].rgbScale;
816 void Film::SetGroupTemperature(u_int index, float value)
818 if (index >= bufferGroups.size())
820 bufferGroups[index].temperature = value;
821 ComputeGroupScale(index);
823 float Film::GetGroupTemperature(u_int index) const
825 if (index >= bufferGroups.size())
827 return bufferGroups[index].temperature;
829 void Film::ComputeGroupScale(u_int index)
831 const XYZColor white(colorSpace.ToXYZ(RGBColor(1.f)));
832 if (bufferGroups[index].temperature > 0.f) {
833 XYZColor colorTemp(BlackbodySPD(bufferGroups[index].temperature));
834 colorTemp /= colorTemp.Y();
835 bufferGroups[index].convert = ColorAdaptator(white,
836 colorSpace.ToXYZ(bufferGroups[index].rgbScale)) *
837 ColorAdaptator(white, colorTemp);
839 bufferGroups[index].convert = ColorAdaptator(white,
840 colorSpace.ToXYZ(bufferGroups[index].rgbScale));
842 bufferGroups[index].convert *= bufferGroups[index].globalScale;
845 void Film::GetSampleExtent(int *xstart, int *xend,
846 int *ystart, int *yend) const
848 *xstart = Floor2Int(xPixelStart + .5f - filter->xWidth);
849 *xend = Floor2Int(xPixelStart + .5f + xPixelCount + filter->xWidth);
850 *ystart = Floor2Int(yPixelStart + .5f - filter->yWidth);
851 *yend = Floor2Int(yPixelStart + .5f + yPixelCount + filter->yWidth);
854 void Film::AddSampleCount(float count) {
856 // Check if we have met the enough rendering time condition
858 boost::xtime_get(&t, boost::TIME_UTC);
859 if (t.sec - creationTime.sec > haltTime)
860 enoughSamplesPerPixel = true;
863 numberOfLocalSamples += count;
865 for (u_int i = 0; i < bufferGroups.size(); ++i) {
866 bufferGroups[i].numberOfSamples += count;
868 // Dade - check if we have enough samples per pixel. The rendering stop
869 // when one of the buffer groups has enough samples (at the moment all
870 // buffer groups have always the same samples count; in the future
871 // it could be better to stop when all buffer groups have enough samples)
872 if ((haltSamplesPerPixel > 0) &&
873 (bufferGroups[i].numberOfSamples >= haltSamplesPerPixel * samplePerPass))
874 enoughSamplesPerPixel = true;
879 std::vector<Film::OutlierAccel>& Film::GetOutlierAccelRow(u_int oY, u_int tileIndex, u_int tileStart, u_int tileEnd)
881 if (oY < tileStart) {
882 // above currrent tile
883 return tileborder_outliers[2 * tileIndex];
884 } else if (oY >= tileEnd) {
885 // below current tile
886 return tileborder_outliers[2 * tileIndex + 1];
889 // inside current tile
893 void Film::RejectTileOutliers(const Contribution* const contribs, u_int num_contribs, u_int tileIndex, int yTilePixelStart, int yTilePixelEnd)
896 const float fnormTileStart = (yTilePixelStart + filter->yWidth) * outlierInvCellHeight;
897 const float fnormTileEnd = (yTilePixelEnd + filter->yWidth) * outlierInvCellHeight;
899 const u_int tileStart = static_cast<u_int>(max(0, min(Floor2Int(fnormTileStart), static_cast<int>(outliers.size() - 1))));
900 const u_int tileEnd = static_cast<u_int>(max(0, min(Floor2Int(fnormTileEnd), static_cast<int>(outliers.size() - 1))));
902 for (u_int ci = 0; ci < num_contribs; ++ci) {
903 const Contribution &contrib(contribs[ci]);
905 // filter-normalized pixel coordinates
906 const float fnormX = (contrib.imageX - 0.5f + filter->xWidth) * outlierInvCellWidth;
907 const float fnormY = (contrib.imageY - 0.5f + filter->yWidth) * outlierInvCellHeight;
909 OutlierData sd(fnormX, fnormY, contrib.color);
911 // perform lookup based on original position
912 // constrain to tile only if we need to add the outlier
913 const int oY = max(0, min(Floor2Int(fnormY), static_cast<int>(outliers.size() - 1)));
914 const int oX = max(0, min(Floor2Int(fnormX), static_cast<int>(outliers[0].size() - 1)));
916 std::vector<OutlierAccel> &outlierRow = GetOutlierAccelRow(oY, tileIndex, tileStart, tileEnd);
917 OutlierAccel &outlierAccel = outlierRow[oX];
919 NearSetPointProcess<OutlierData::Point_t> proc(outlierRejection_k);
920 vector<ClosePoint<OutlierData::Point_t> > closest(outlierRejection_k);
921 proc.points = &closest[0];
923 float maxDist = INFINITY;
925 outlierAccel.Lookup(sd.p, proc, maxDist);
927 float kmeandist = 0.f;
928 for (u_int i = 0; i < proc.foundPoints; i++)
929 kmeandist += proc.points[i].distance;
931 //kmeandist /= proc.foundPoints;
933 if (proc.foundPoints < 1 || kmeandist > proc.foundPoints) { // kmeandist > 1.f
934 // add outlier and return
935 // include surrounding cells so we don't have to
936 // traverse multiple cells for each lookup
937 const u_int oLeft = static_cast<u_int>(max(0, oX - 1));
938 const u_int oRight = static_cast<u_int>(min(static_cast<int>(outliers[0].size() - 1), oX + 1));
939 const u_int oTop = static_cast<u_int>(max(0, oY - 1));
940 const u_int oBottom = static_cast<u_int>(min(static_cast<int>(outliers.size() - 1), oY + 1));
942 if (oTop < tileStart || oBottom >= tileEnd) {
943 // outlier spans tile borders
944 for (u_int i = oTop; i <= oBottom; ++i) {
945 std::vector<OutlierAccel> &row = GetOutlierAccelRow(oY, tileIndex, tileStart, tileEnd);
946 for (u_int j = oLeft; j <= oRight; ++j) {
947 row[j].AddNode(sd.p);
951 // we're all inside one tile
952 for (u_int i = oTop; i <= oBottom; ++i) {
953 std::vector<OutlierAccel> &row = outliers[oY];
954 for (u_int j = oLeft; j <= oRight; ++j) {
955 row[j].AddNode(sd.p);
960 contrib.variance = -1.f;
962 // not an outlier, splat
966 u_int Film::GetTileCount() const {
970 u_int Film::GetTileIndexes(const Contribution &contrib, u_int *tile0, u_int *tile1) const {
971 const float imageY = contrib.imageY + tileOffset;
973 const float tileY = imageY * invTileHeight;
975 *tile0 = static_cast<u_int>(Clamp(static_cast<int>(tileY), 0, static_cast<int>(tileCount-1)));
978 if (*tile1 >= tileCount || tileY + tileOffset2 < *tile1)
984 void Film::GetTileExtent(u_int tileIndex, int *xstart, int *xend, int *ystart, int *yend) const {
985 *xstart = xPixelStart;
986 *xend = xPixelStart + xPixelCount;
987 *ystart = yPixelStart + min(tileIndex * tileHeight, yPixelCount);
988 *yend = yPixelStart + min((tileIndex+1) * tileHeight, yPixelCount);
992 void Film::AddTileSamples(const Contribution* const contribs, u_int num_contribs, u_int tileIndex) {
994 int xTilePixelStart, xTilePixelEnd;
995 int yTilePixelStart, yTilePixelEnd;
996 GetTileExtent(tileIndex, &xTilePixelStart, &xTilePixelEnd, &yTilePixelStart, &yTilePixelEnd);
998 if (outlierRejection_k > 0) {
999 // reject outliers by setting their weight (variance field) to -1
1000 RejectTileOutliers(contribs, num_contribs, tileIndex, yTilePixelStart, yTilePixelEnd);
1004 for (u_int ci = 0; ci < num_contribs; ci++) {
1005 const Contribution &contrib(contribs[ci]);
1007 XYZColor xyz = contrib.color;
1008 const float alpha = contrib.alpha;
1009 const float weight = contrib.variance;
1011 // negative weight means sample was rejected
1012 // so do this test first
1013 if (!(weight >= 0.f) || isinf(weight)) {
1014 if(debug_mode && (weight >= 0.f)) {
1015 LOG(LUX_WARNING,LUX_LIMIT) << "Out of bound weight in Film::AddSample: "
1016 << weight << ", sample discarded";
1021 // Issue warning if unexpected radiance value returned
1022 if (!(xyz.Y() >= 0.f) || isinf(xyz.Y())) {
1024 LOG(LUX_WARNING,LUX_LIMIT) << "Out of bound intensity in Film::AddSample: "
1025 << xyz.Y() << ", sample discarded";
1030 if (!(alpha >= 0.f) || isinf(alpha)) {
1032 LOG(LUX_WARNING,LUX_LIMIT) << "Out of bound alpha in Film::AddSample: "
1033 << alpha << ", sample discarded";
1038 if (premultiplyAlpha)
1041 BufferGroup ¤tGroup = bufferGroups[contrib.bufferGroup];
1042 Buffer *buffer = currentGroup.getBuffer(contrib.buffer);
1044 // Compute sample's raster extent
1045 float dImageX = contrib.imageX - 0.5f;
1046 float dImageY = contrib.imageY - 0.5f;
1048 // Get filter coefficients
1049 const FilterLUT &filterLUT =
1050 filterLUTs->GetLUT(dImageX - Floor2Int(contrib.imageX), dImageY - Floor2Int(contrib.imageY));
1051 const float *lut = filterLUT.GetLUT();
1053 int x0 = Ceil2Int (dImageX - filter->xWidth);
1054 int x1 = x0 + filterLUT.GetWidth();
1055 int y0 = Ceil2Int (dImageY - filter->yWidth);
1056 int y1 = y0 + filterLUT.GetHeight();
1057 if (x1 < x0 || y1 < y0 || x1 < 0 || y1 < 0)
1060 const u_int xStart = static_cast<u_int>(max(x0, xTilePixelStart));
1061 const u_int yStart = static_cast<u_int>(max(y0, yTilePixelStart));
1062 const u_int xEnd = static_cast<u_int>(min(x1, xTilePixelEnd));
1063 const u_int yEnd = static_cast<u_int>(min(y1, yTilePixelEnd));
1065 for (u_int y = yStart; y < yEnd; ++y) {
1066 const int yoffset = (y-y0) * filterLUT.GetWidth();
1067 for (u_int x = xStart; x < xEnd; ++x) {
1068 // Evaluate filter value at $(x,y)$ pixel
1069 const int xoffset = x-x0;
1070 const float filterWt = lut[yoffset + xoffset];
1071 // Update pixel values with filtered sample contribution
1072 buffer->Add(x - xPixelStart,y - yPixelStart,
1073 xyz, alpha, filterWt * weight);
1074 // Update ZBuffer values with filtered zdepth contribution
1075 if(use_Zbuf && contrib.zdepth != 0.f)
1076 ZBuffer->Add(x - xPixelStart, y - yPixelStart, contrib.zdepth, 1.0f);
1082 void Film::AddSample(Contribution *contrib) {
1083 u_int tileIndex0, tileIndex1;
1084 u_int tiles = GetTileIndexes(*contrib, &tileIndex0, &tileIndex1);
1085 AddTileSamples(contrib, 1, tileIndex0);
1087 AddTileSamples(contrib, 1, tileIndex1);
1090 void Film::SetSample(const Contribution *contrib) {
1091 XYZColor xyz = contrib->color;
1092 const float alpha = contrib->alpha;
1093 const float weight = contrib->variance;
1094 const int x = static_cast<int>(contrib->imageX);
1095 const int y = static_cast<int>(contrib->imageY);
1097 if (x < static_cast<int>(xPixelStart) || x >= static_cast<int>(xPixelStart + xPixelCount) ||
1098 y < static_cast<int>(yPixelStart) || y >= static_cast<int>(yPixelStart + yPixelCount)) {
1100 LOG(LUX_WARNING, LUX_LIMIT) << "Out of bound pixel coordinates in Film::SetSample: ("
1101 << x << ", " << y << "), sample discarded";
1106 // Issue warning if unexpected radiance value returned
1107 if (!(xyz.Y() >= 0.f) || isinf(xyz.Y())) {
1109 LOG(LUX_WARNING, LUX_LIMIT) << "Out of bound intensity in Film::SetSample: "
1110 << xyz.Y() << ", sample discarded";
1115 if (!(alpha >= 0.f) || isinf(alpha)) {
1117 LOG(LUX_WARNING, LUX_LIMIT) << "Out of bound alpha in Film::SetSample: "
1118 << alpha << ", sample discarded";
1123 if (!(weight >= 0.f) || isinf(weight)) {
1125 LOG(LUX_WARNING, LUX_LIMIT) << "Out of bound weight in Film::SetSample: "
1126 << weight << ", sample discarded";
1131 /*FIXME restore the functionality
1132 // Reject samples higher than max Y() after warmup period
1133 if (warmupComplete) {
1137 maxY = max(maxY, xyz.Y());
1139 if (warmupSamples >= reject_warmup_samples)
1140 warmupComplete = true;
1144 if (premultiplyAlpha)
1147 BufferGroup ¤tGroup = bufferGroups[contrib->bufferGroup];
1148 Buffer *buffer = currentGroup.getBuffer(contrib->buffer);
1150 buffer->Set(x, y, xyz, alpha);
1152 // Update ZBuffer values with filtered zdepth contribution
1153 if(use_Zbuf && contrib->zdepth != 0.f)
1154 ZBuffer->Add(x, y, contrib->zdepth, 1.0f);
1157 void Film::WriteResumeFilm(const string &filename)
1159 string fullfilename = boost::filesystem::complete(boost::filesystem::path(filename, boost::filesystem::native), boost::filesystem::current_path()).file_string();
1160 // Dade - save the status of the film to the file
1161 LOG(LUX_INFO, LUX_NOERROR) << "Writing resume film file";
1163 const string tempfilename = fullfilename + ".temp";
1165 std::ofstream filestr(tempfilename.c_str(), std::ios_base::out | std::ios_base::binary);
1167 LOG(LUX_SEVERE,LUX_SYSTEM) << "Cannot open file '" << tempfilename << "' for writing resume film";
1172 bool writeSuccessful = TransmitFilm(filestr,false,true, true, writeFlmDirect);
1176 if (writeSuccessful) {
1178 #if !defined(BOOST_FILESYSTEM_VERSION) || (BOOST_FILESYSTEM_VERSION < 3)
1179 // boost filesystem v2 does not have POSIX compliant rename()
1180 if (boost::filesystem::exists(fullfilename))
1181 boost::filesystem::remove(fullfilename);
1183 boost::filesystem::rename(tempfilename, fullfilename);
1184 LOG(LUX_INFO, LUX_NOERROR) << "Resume film written to '" << fullfilename << "'";
1185 } catch (std::runtime_error e) {
1186 LOG(LUX_ERROR, LUX_SYSTEM) <<
1187 "Failed to rename new resume film, leaving new resume film as '" << tempfilename << "' (" << e.what() << ")";
1200 * magic_number - int - the magic number number
1201 * version_number - int - the version number
1202 * x_resolution - int - the x resolution of the buffers
1203 * y_resolution - int - the y resolution of the buffers
1204 * #buffer_groups - u_int - the number of lightgroups
1205 * #buffer_configs - u_int - the number of buffers per light group
1206 * for i in 1:#buffer_configs
1207 * buffer_type - int - the type of the i'th buffer
1208 * #parameters - u_int - the number of stored parameters
1209 * for i in 1:#parameters
1210 * param_type - int - the type of the i'th parameter
1211 * param_size - int - the size of the value of the i'th parameter in bytes
1212 * param_id - int - the id of the i'th parameter
1213 * param_index - int - the index of the i'th parameter
1214 * param_value - * - the value of the i'th parameter
1217 * for i in 1:#buffer_groups
1218 * #samples - float - the number of samples in the i'th buffer group
1219 * for j in 1:#buffer_configs
1220 * for y in 1:y_resolution
1221 * for x in 1:x_resolution
1222 * X - float - the weighted sum of all X values added to the pixel
1223 * Y - float - the weighted sum of all Y values added to the pixel
1224 * Z - float - the weighted sum of all Z values added to the pixel
1225 * alpha - float - the weighted sum of all alpha values added to the pixel
1226 * weight_sum - float - the sum of al weights of all values added to the pixel
1230 * - data is written as binary little-endian
1232 * - the version is not intended for backward/forward compatibility but just as a check
1234 static const int FLM_MAGIC_NUMBER = 0xCEBCD816;
1235 static const int FLM_VERSION = 0; // should be incremented on each change to the format to allow detecting unsupported FLM data!
1236 enum FlmParameterType {
1237 FLM_PARAMETER_TYPE_FLOAT = 0,
1238 FLM_PARAMETER_TYPE_STRING = 1,
1239 FLM_PARAMETER_TYPE_DOUBLE = 2
1242 class FlmParameter {
1245 FlmParameter(Film *aFilm, FlmParameterType aType, luxComponentParameters aParam, u_int aIndex) {
1250 case FLM_PARAMETER_TYPE_FLOAT:
1252 floatValue = static_cast<float>(aFilm->GetParameterValue(aParam, aIndex));
1254 case FLM_PARAMETER_TYPE_DOUBLE:
1256 floatValue = static_cast<double>(aFilm->GetParameterValue(aParam, aIndex));
1258 case FLM_PARAMETER_TYPE_STRING:
1259 stringValue = aFilm->GetStringParameterValue(aParam, aIndex);
1260 size = stringValue.size();
1263 LOG(LUX_ERROR,LUX_SYSTEM) << "Invalid parameter type (expected value in [0,2], got=" << type << ")";
1269 void Set(Film *aFilm) {
1271 case FLM_PARAMETER_TYPE_FLOAT:
1272 aFilm->SetParameterValue(id, floatValue, index);
1274 case FLM_PARAMETER_TYPE_DOUBLE:
1275 aFilm->SetParameterValue(id, floatValue, index);
1277 case FLM_PARAMETER_TYPE_STRING:
1278 aFilm->SetStringParameterValue(id, stringValue, index);
1281 // ignore invalid type (already reported in constructor)
1286 bool Read(std::basic_istream<char> &is, bool isLittleEndian, Film *film ) {
1288 tmpType = osReadLittleEndianInt(isLittleEndian, is);
1289 type = FlmParameterType(tmpType);
1291 LOG(LUX_ERROR,LUX_SYSTEM)<< "Error while receiving film";
1294 size = osReadLittleEndianUInt(isLittleEndian, is);
1296 LOG(LUX_ERROR,LUX_SYSTEM)<< "Error while receiving film";
1299 id = static_cast<luxComponentParameters>(osReadLittleEndianInt(isLittleEndian, is));
1301 LOG(LUX_ERROR,LUX_SYSTEM)<< "Error while receiving film";
1304 index = osReadLittleEndianUInt(isLittleEndian, is);
1306 LOG(LUX_ERROR,LUX_SYSTEM)<< "Error while receiving film";
1310 case FLM_PARAMETER_TYPE_FLOAT:
1312 LOG(LUX_ERROR,LUX_SYSTEM) << "Invalid parameter size (expected value for float is 4, received=" << size << ")";
1315 floatValue = osReadLittleEndianFloat(isLittleEndian, is);
1317 case FLM_PARAMETER_TYPE_DOUBLE:
1319 LOG(LUX_ERROR,LUX_SYSTEM) << "Invalid parameter size (expected value for double is 8, received=" << size << ")";
1322 floatValue = osReadLittleEndianDouble(isLittleEndian, is);
1324 case FLM_PARAMETER_TYPE_STRING: {
1325 char* chars = new char[size+1];
1326 is.read(chars, size);
1328 stringValue = string(chars);
1333 LOG(LUX_ERROR,LUX_SYSTEM) << "Invalid parameter type (expected value in [0,1], received=" << tmpType << ")";
1339 void Write(std::basic_ostream<char> &os, bool isLittleEndian) const {
1340 osWriteLittleEndianInt(isLittleEndian, os, type);
1341 osWriteLittleEndianUInt(isLittleEndian, os, size);
1342 osWriteLittleEndianInt(isLittleEndian, os, id);
1343 osWriteLittleEndianUInt(isLittleEndian, os, index);
1345 case FLM_PARAMETER_TYPE_FLOAT:
1346 osWriteLittleEndianFloat(isLittleEndian, os, floatValue);
1348 case FLM_PARAMETER_TYPE_DOUBLE:
1349 osWriteLittleEndianDouble(isLittleEndian, os, floatValue);
1351 case FLM_PARAMETER_TYPE_STRING:
1352 os.write(stringValue.c_str(), size);
1355 // ignore invalid type (already reported in constructor)
1361 FlmParameterType type;
1363 luxComponentParameters id;
1373 bool Read(filtering_stream<input> &in, bool isLittleEndian, Film *film );
1374 void Write(std::basic_ostream<char> &os, bool isLittleEndian) const;
1380 u_int numBufferGroups;
1381 u_int numBufferConfigs;
1382 vector<int> bufferTypes;
1384 vector<FlmParameter> params;
1387 bool FlmHeader::Read(filtering_stream<input> &in, bool isLittleEndian, Film *film ) {
1388 // Read and verify magic number and version
1389 magicNumber = osReadLittleEndianInt(isLittleEndian, in);
1391 LOG(LUX_ERROR,LUX_SYSTEM)<< "Error while receiving film";
1394 if (magicNumber != FLM_MAGIC_NUMBER) {
1395 LOG(LUX_ERROR,LUX_SYSTEM) << "Invalid FLM magic number (expected=" << FLM_MAGIC_NUMBER
1396 << ", received=" << magicNumber << ")";
1399 versionNumber = osReadLittleEndianInt(isLittleEndian, in);
1401 LOG(LUX_ERROR,LUX_SYSTEM)<< "Error while receiving film";
1404 if (versionNumber != FLM_VERSION) {
1405 LOG(LUX_ERROR,LUX_SYSTEM) << "Invalid FLM version (expected=" << FLM_VERSION
1406 << ", received=" << versionNumber << ")";
1409 // Read and verify the buffer resolution
1410 xResolution = osReadLittleEndianUInt(isLittleEndian, in);
1411 yResolution = osReadLittleEndianUInt(isLittleEndian, in);
1412 if (xResolution == 0 || yResolution == 0 ) {
1413 LOG(LUX_ERROR,LUX_SYSTEM)
1414 << "Invalid resolution (expected positive resolution, received="
1415 << xResolution << "x" << yResolution
1420 (xResolution != film->GetXPixelCount() ||
1421 yResolution != film->GetYPixelCount())) {
1422 LOG(LUX_ERROR,LUX_SYSTEM)
1423 << "Invalid resolution (expected=" << film->GetXPixelCount() << "x" << film->GetYPixelCount()
1424 << ", received=" << xResolution << "x" << yResolution << ")";
1427 // Read and verify #buffer groups and buffer configs
1428 numBufferGroups = osReadLittleEndianUInt(isLittleEndian, in);
1430 LOG(LUX_ERROR,LUX_SYSTEM)<< "Error while receiving film";
1433 if (film != NULL && numBufferGroups != film->GetNumBufferGroups()) {
1434 LOG(LUX_ERROR,LUX_SYSTEM)
1435 << "Invalid number of buffer groups (expected=" << film->GetNumBufferGroups()
1436 << ", received=" << numBufferGroups << ")";
1439 numBufferConfigs = osReadLittleEndianUInt(isLittleEndian, in);
1441 LOG(LUX_ERROR,LUX_SYSTEM)<< "Error while receiving film";
1444 if (film != NULL && numBufferConfigs != film->GetNumBufferConfigs()) {
1445 LOG(LUX_ERROR,LUX_SYSTEM)
1446 << "Invalid number of buffers (expected=" << film->GetNumBufferConfigs()
1447 << ", received=" << numBufferConfigs << ")";
1450 for (u_int i = 0; i < numBufferConfigs; ++i) {
1452 type = osReadLittleEndianInt(isLittleEndian, in);
1454 LOG(LUX_ERROR,LUX_SYSTEM)<< "Error while receiving film";
1457 if (type < 0 || type >= NUM_OF_BUFFER_TYPES) {
1458 LOG(LUX_ERROR,LUX_SYSTEM) << "Invalid buffer type for buffer " << i << "(expected number in [0," << NUM_OF_BUFFER_TYPES << "[, received=" << type << ")";
1461 if (film != NULL && type != film->GetBufferConfig(i).type) {
1462 LOG(LUX_ERROR,LUX_SYSTEM) << "Invalid buffer type for buffer " << i << " (expected=" << film->GetBufferConfig(i).type
1463 << ", received=" << type << ")";
1466 bufferTypes.push_back(type);
1469 numParams = osReadLittleEndianUInt(isLittleEndian, in);
1471 LOG( LUX_ERROR,LUX_SYSTEM)<< "Error while receiving film";
1474 params.reserve(numParams);
1475 for(u_int i = 0; i < numParams; ++i) {
1477 bool ok = param.Read(in, isLittleEndian, film);
1479 LOG( LUX_ERROR,LUX_SYSTEM)<< "Error while receiving film";
1483 //LOG(LUX_ERROR,LUX_SYSTEM) << "Invalid parameter (id=" << param.id << ", index=" << param.index << ", value=" << param.value << ")";
1486 params.push_back(param);
1491 void FlmHeader::Write(std::basic_ostream<char> &os, bool isLittleEndian) const
1493 // Write magic number and version
1494 osWriteLittleEndianInt(isLittleEndian, os, magicNumber);
1495 osWriteLittleEndianInt(isLittleEndian, os, versionNumber);
1496 // Write buffer resolution
1497 osWriteLittleEndianUInt(isLittleEndian, os, xResolution);
1498 osWriteLittleEndianUInt(isLittleEndian, os, yResolution);
1499 // Write #buffer groups and buffer configs for verification
1500 osWriteLittleEndianUInt(isLittleEndian, os, numBufferGroups);
1501 osWriteLittleEndianUInt(isLittleEndian, os, numBufferConfigs);
1502 for (u_int i = 0; i < numBufferConfigs; ++i)
1503 osWriteLittleEndianInt(isLittleEndian, os, bufferTypes[i]);
1505 osWriteLittleEndianUInt(isLittleEndian, os, numParams);
1506 for(u_int i = 0; i < numParams; ++i) {
1507 params[i].Write(os, isLittleEndian);
1511 double Film::DoTransmitFilm(
1512 std::basic_ostream<char> &os,
1514 bool transmitParams)
1516 const bool isLittleEndian = osIsLittleEndian();
1518 LOG(LUX_DEBUG,LUX_NOERROR)<< "Transmitting film (little endian=" <<(isLittleEndian ? "true" : "false") << ")";
1522 header.magicNumber = FLM_MAGIC_NUMBER;
1523 header.versionNumber = FLM_VERSION;
1524 header.xResolution = xPixelCount;
1525 header.yResolution = yPixelCount;
1526 header.numBufferGroups = bufferGroups.size();
1527 header.numBufferConfigs = bufferConfigs.size();
1528 for (u_int i = 0; i < bufferConfigs.size(); ++i)
1529 header.bufferTypes.push_back(bufferConfigs[i].type);
1531 if (transmitParams) {
1532 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_TM_TONEMAPKERNEL, 0));
1534 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_TM_REINHARD_PRESCALE, 0));
1535 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_TM_REINHARD_POSTSCALE, 0));
1536 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_TM_REINHARD_BURN, 0));
1538 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_TM_LINEAR_SENSITIVITY, 0));
1539 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_TM_LINEAR_EXPOSURE, 0));
1540 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_TM_LINEAR_FSTOP, 0));
1541 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_TM_LINEAR_GAMMA, 0));
1543 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_TM_CONTRAST_YWA, 0));
1545 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_LDR_CLAMP_METHOD, 0));
1547 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_TORGB_X_WHITE, 0));
1548 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_TORGB_Y_WHITE, 0));
1549 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_TORGB_X_RED, 0));
1550 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_TORGB_Y_RED, 0));
1551 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_TORGB_X_GREEN, 0));
1552 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_TORGB_Y_GREEN, 0));
1553 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_TORGB_X_BLUE, 0));
1554 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_TORGB_Y_BLUE, 0));
1555 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_TORGB_GAMMA, 0));
1557 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_CAMERA_RESPONSE_ENABLED, 0));
1558 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_STRING, LUX_FILM_CAMERA_RESPONSE_FILE, 0));
1560 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_UPDATEBLOOMLAYER, 0));
1561 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_DELETEBLOOMLAYER, 0));
1562 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_BLOOMRADIUS, 0));
1563 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_BLOOMWEIGHT, 0));
1565 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_VIGNETTING_ENABLED, 0));
1566 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_VIGNETTING_SCALE, 0));
1568 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_ABERRATION_ENABLED, 0));
1569 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_ABERRATION_AMOUNT, 0));
1571 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_UPDATEGLARELAYER, 0));
1572 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_DELETEGLARELAYER, 0));
1573 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_GLARE_AMOUNT, 0));
1574 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_GLARE_RADIUS, 0));
1575 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_GLARE_BLADES, 0));
1576 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_GLARE_THRESHOLD, 0));
1578 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_NOISE_CHIU_ENABLED, 0));
1579 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_NOISE_CHIU_RADIUS, 0));
1580 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_NOISE_CHIU_INCLUDECENTER, 0));
1582 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_NOISE_GREYC_ENABLED, 0));
1583 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_NOISE_GREYC_AMPLITUDE, 0));
1584 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_NOISE_GREYC_NBITER, 0));
1585 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_NOISE_GREYC_SHARPNESS, 0));
1586 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_NOISE_GREYC_ANISOTROPY, 0));
1587 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_NOISE_GREYC_ALPHA, 0));
1588 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_NOISE_GREYC_SIGMA, 0));
1589 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_NOISE_GREYC_FASTAPPROX, 0));
1590 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_NOISE_GREYC_GAUSSPREC, 0));
1591 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_NOISE_GREYC_DL, 0));
1592 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_NOISE_GREYC_DA, 0));
1593 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_NOISE_GREYC_INTERP, 0));
1594 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_NOISE_GREYC_TILE, 0));
1595 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_NOISE_GREYC_BTILE, 0));
1596 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_NOISE_GREYC_THREADS, 0));
1598 for(u_int i = 0; i < GetNumBufferGroups(); ++i) {
1599 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_LG_SCALE, i));
1600 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_LG_ENABLE, i));
1601 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_LG_SCALE_RED, i));
1602 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_LG_SCALE_GREEN, i));
1603 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_LG_SCALE_BLUE, i));
1604 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_FLOAT, LUX_FILM_LG_TEMPERATURE, i));
1606 header.params.push_back(FlmParameter(this, FLM_PARAMETER_TYPE_STRING, LUX_FILM_LG_NAME, i));
1609 header.numParams = header.params.size();
1611 header.numParams = 0;
1613 header.Write(os, isLittleEndian);
1615 // Write each buffer group
1616 double totNumberOfSamples = 0.;
1617 for (u_int i = 0; i < bufferGroups.size(); ++i) {
1618 BufferGroup& bufferGroup = bufferGroups[i];
1619 // Write number of samples
1620 osWriteLittleEndianDouble(isLittleEndian, os, bufferGroup.numberOfSamples);
1622 // Write each buffer
1623 for (u_int j = 0; j < bufferConfigs.size(); ++j) {
1624 Buffer* buffer = bufferGroup.getBuffer(j);
1627 const BlockedArray<Pixel>* pixelBuf = buffer->pixels;
1628 for (u_int y = 0; y < pixelBuf->vSize(); ++y) {
1629 for (u_int x = 0; x < pixelBuf->uSize(); ++x) {
1630 const Pixel &pixel = (*pixelBuf)(x, y);
1631 osWriteLittleEndianFloat(isLittleEndian, os, pixel.L.c[0]);
1632 osWriteLittleEndianFloat(isLittleEndian, os, pixel.L.c[1]);
1633 osWriteLittleEndianFloat(isLittleEndian, os, pixel.L.c[2]);
1634 osWriteLittleEndianFloat(isLittleEndian, os, pixel.alpha);
1635 osWriteLittleEndianFloat(isLittleEndian, os, pixel.weightSum);
1638 // error during transmission, abort
1643 totNumberOfSamples += bufferGroup.numberOfSamples;
1644 LOG(LUX_DEBUG,LUX_NOERROR) << "Transmitted " << bufferGroup.numberOfSamples << " samples for buffer group " << i <<
1645 " (buffer config size: " << bufferConfigs.size() << ")";
1648 // transmitted everything, now we can clear buffers if needed
1650 for (u_int i = 0; i < bufferGroups.size(); ++i) {
1652 BufferGroup& bufferGroup = bufferGroups[i];
1654 for (u_int j = 0; j < bufferConfigs.size(); ++j) {
1655 Buffer* buffer = bufferGroup.getBuffer(j);
1657 // Dade - reset the rendering buffer
1661 // Dade - reset the rendering buffer
1662 bufferGroup.numberOfSamples = 0;
1666 return totNumberOfSamples;
1670 bool Film::TransmitFilm(
1671 std::basic_ostream<char> &stream,
1673 bool transmitParams,
1674 bool useCompression,
1677 std::streamsize size;
1679 double totNumberOfSamples = 0;
1681 bool transmitError = false;
1684 //std::stringstream ss(std::stringstream::in | std::stringstream::out | std::stringstream::binary);
1685 multibuffer_device mbdev;
1686 boost::iostreams::stream<multibuffer_device> ms(mbdev);
1688 totNumberOfSamples = DoTransmitFilm(ms, clearBuffers, transmitParams);
1690 transmitError = !ms.good();
1692 ms.seekg(0, BOOST_IOS::beg);
1694 if (!transmitError) {
1695 if (useCompression) {
1696 filtering_streambuf<input> in;
1697 in.push(gzip_compressor(4));
1699 size = boost::iostreams::copy(in, stream);
1701 size = boost::iostreams::copy(ms, stream);
1703 // ignore how the copy to stream goes for now, as
1704 // direct writing won't help with that
1706 LOG(LUX_SEVERE,LUX_SYSTEM) << "Error while preparing film data for transmission, retrying without buffering.";
1710 // if the memory buffered method fails it's most likely due
1711 // to low memory conditions, so fall back to direct writing
1712 if (directWrite || transmitError) {
1713 std::streampos stream_startpos = stream.tellp();
1714 if (useCompression) {
1715 filtering_stream<output> fs;
1716 fs.push(gzip_compressor(4));
1718 totNumberOfSamples = DoTransmitFilm(fs, clearBuffers, transmitParams);
1722 transmitError = !fs.good();
1724 totNumberOfSamples = DoTransmitFilm(stream, clearBuffers, transmitParams);
1725 transmitError = !stream.good();
1727 size = stream.tellp() - stream_startpos;
1730 if (transmitError || !stream.good()) {
1731 LOG(LUX_SEVERE,LUX_SYSTEM) << "Error while transmitting film";
1734 LOG(LUX_DEBUG,LUX_NOERROR) << "Transmitted a film with " << totNumberOfSamples << " samples";
1736 LOG(LUX_INFO,LUX_NOERROR) << "Film transmission done (" << (size / 1024) << " Kbytes sent)";
1741 double Film::UpdateFilm(std::basic_istream<char> &stream) {
1742 const bool isLittleEndian = osIsLittleEndian();
1744 filtering_stream<input> in;
1745 in.push(gzip_decompressor());
1748 LOG(LUX_DEBUG,LUX_NOERROR) << "Receiving film (little endian=" << (isLittleEndian ? "true" : "false") << ")";
1752 if (!header.Read(in, isLittleEndian, this))
1755 // Read buffer groups
1756 vector<double> bufferGroupNumSamples(bufferGroups.size());
1757 vector<BlockedArray<Pixel>*> tmpPixelArrays(bufferGroups.size() * bufferConfigs.size());
1758 for (u_int i = 0; i < bufferGroups.size(); i++) {
1759 double numberOfSamples;
1760 numberOfSamples = osReadLittleEndianDouble(isLittleEndian, in);
1763 bufferGroupNumSamples[i] = numberOfSamples;
1766 for(u_int j = 0; j < bufferConfigs.size(); ++j) {
1767 const Buffer* localBuffer = bufferGroups[i].getBuffer(j);
1769 BlockedArray<Pixel> *tmpPixelArr = new BlockedArray<Pixel>(
1770 localBuffer->xPixelCount, localBuffer->yPixelCount);
1771 tmpPixelArrays[i*bufferConfigs.size() + j] = tmpPixelArr;
1772 for (u_int y = 0; y < tmpPixelArr->vSize(); ++y) {
1773 for (u_int x = 0; x < tmpPixelArr->uSize(); ++x) {
1774 Pixel &pixel = (*tmpPixelArr)(x, y);
1775 pixel.L.c[0] = osReadLittleEndianFloat(isLittleEndian, in);
1776 pixel.L.c[1] = osReadLittleEndianFloat(isLittleEndian, in);
1777 pixel.L.c[2] = osReadLittleEndianFloat(isLittleEndian, in);
1778 pixel.alpha = osReadLittleEndianFloat(isLittleEndian, in);
1779 pixel.weightSum = osReadLittleEndianFloat(isLittleEndian, in);
1788 LOG( LUX_DEBUG,LUX_NOERROR)
1789 << "Received " << bufferGroupNumSamples[i] << " samples for buffer group " << i
1790 << " (buffer config size: " << bufferConfigs.size() << ")";
1793 // Dade - check for errors
1794 double totNumberOfSamples = 0.;
1795 double maxTotNumberOfSamples = 0.;
1797 // Update parameters
1798 for (vector<FlmParameter>::iterator it = header.params.begin(); it != header.params.end(); ++it)
1801 // Dade - add all received data
1802 for (u_int i = 0; i < bufferGroups.size(); ++i) {
1803 BufferGroup ¤tGroup = bufferGroups[i];
1804 for (u_int j = 0; j < bufferConfigs.size(); ++j) {
1805 const BlockedArray<Pixel> *receivedPixels = tmpPixelArrays[ i * bufferConfigs.size() + j ];
1806 Buffer *buffer = currentGroup.getBuffer(j);
1808 for (u_int y = 0; y < buffer->yPixelCount; ++y) {
1809 for (u_int x = 0; x < buffer->xPixelCount; ++x) {
1810 const Pixel &pixel = (*receivedPixels)(x, y);
1811 Pixel &pixelResult = (*buffer->pixels)(x, y);
1812 pixelResult.L.c[0] += pixel.L.c[0];
1813 pixelResult.L.c[1] += pixel.L.c[1];
1814 pixelResult.L.c[2] += pixel.L.c[2];
1815 pixelResult.alpha += pixel.alpha;
1816 pixelResult.weightSum += pixel.weightSum;
1821 currentGroup.numberOfSamples += bufferGroupNumSamples[i];
1822 // Check if we have enough samples per pixel
1823 if ((haltSamplesPerPixel > 0) &&
1824 (currentGroup.numberOfSamples >= haltSamplesPerPixel * samplePerPass))
1825 enoughSamplesPerPixel = true;
1826 totNumberOfSamples += bufferGroupNumSamples[i];
1827 maxTotNumberOfSamples = max(maxTotNumberOfSamples, bufferGroupNumSamples[i]);
1830 LOG( LUX_DEBUG,LUX_NOERROR) << "Received film with " << totNumberOfSamples << " samples";
1832 LOG( LUX_ERROR,LUX_SYSTEM)<< "IO error while receiving film buffers";
1835 for (u_int i = 0; i < tmpPixelArrays.size(); ++i)
1836 delete tmpPixelArrays[i];
1838 return maxTotNumberOfSamples;
1841 bool Film::LoadResumeFilm(const string &filename)
1843 // Read the FLM header
1844 std::ifstream stream(filename.c_str(), std::ios_base::in | std::ios_base::binary);
1845 filtering_stream<input> in;
1846 in.push(gzip_decompressor());
1848 const bool isLittleEndian = osIsLittleEndian();
1850 bool headerOk = header.Read(in, isLittleEndian, NULL);
1854 xResolution = static_cast<int>(header.xResolution);
1855 yResolution = static_cast<int>(header.yResolution);
1856 xPixelStart = 0; // by default use full resolution
1858 xPixelCount = xResolution;
1859 yPixelCount = yResolution;
1861 // Create the buffers (also loads the FLM file)
1862 for (u_int i = 0; i < header.numBufferConfigs; ++i)
1863 RequestBuffer(BufferType(header.bufferTypes[i]), BUF_FRAMEBUFFER, "");
1865 vector<string> bufferGroups;
1866 for (u_int i = 0; i < header.numBufferGroups; ++i) {
1867 std::stringstream ss;
1868 ss << "lightgroup #" << (i + 1);
1869 bufferGroups.push_back(ss.str());
1871 RequestBufferGroups(bufferGroups);
1878 void Film::getHistogramImage(unsigned char *outPixels, u_int width, u_int height, int options)
1880 boost::mutex::scoped_lock lock(histMutex);
1882 histogram = new Histogram();
1883 histogram->MakeImage(outPixels, width, height, options);
1886 // Histogram Function Definitions
1888 Histogram::Histogram()
1891 m_displayGamma = 2.2f; //gamma of the display the histogram is viewed on
1893 //calculate visible plot range
1894 float narrowRangeSize = -logf(powf(.5f, 10.f / m_displayGamma)); //size of 10 EV zones, display-gamma corrected
1895 float scalingFactor = 0.75f;
1896 m_lowRange = -(1.f + scalingFactor) * narrowRangeSize;
1897 m_highRange = scalingFactor * narrowRangeSize;
1900 m_newBucketNr = 300;
1902 for (u_int i = 0; i < m_bucketNr * 4; ++i)
1906 Histogram::~Histogram()
1911 void Histogram::CheckBucketNr()
1913 if (m_newBucketNr > 0) { //if nr of buckets changed recalculate data that depends on it
1914 m_bucketNr = m_newBucketNr;
1917 m_buckets = new float[m_bucketNr * 4];
1920 m_bucketSize = (m_highRange - m_lowRange) / m_bucketNr;
1922 //calculate EV zone tick positions
1923 float zoneValue = 1.f;
1924 for (u_int i = 0; i < 11; ++i) {
1925 float value = logf(powf(zoneValue, 1.f / m_displayGamma));
1926 u_int bucket = Clamp(Round2UInt((value - m_lowRange) / m_bucketSize), 0U, m_bucketNr - 1);
1927 m_zones[i] = bucket;
1933 void Histogram::Calculate(vector<RGBColor> &pixels, u_int width, u_int height)
1935 boost::mutex::scoped_lock lock(this->m_mutex);
1936 if (pixels.empty() || width == 0 || height == 0)
1938 u_int pixelNr = width * height;
1944 for (u_int i = 0; i < m_bucketNr * 4; ++i)
1948 for (u_int i = 0; i < pixelNr; ++i) {
1949 for (u_int j = 0; j < 3; ++j){ //each color channel
1950 value = pixels[i].c[j];
1952 value = logf(value);
1953 u_int bucket = Clamp(Round2UInt((value - m_lowRange) / m_bucketSize), 0U, m_bucketNr - 1);
1954 m_buckets[bucket * 4 + j] += 1.f;
1957 value = pixels[i].Y(); //brightness
1959 value = logf(value);
1960 u_int bucket = Clamp(Round2UInt((value - m_lowRange) / m_bucketSize), 0U, m_bucketNr - 1);
1961 m_buckets[bucket * 4 + 3] += 1.f;
1966 void Histogram::MakeImage(unsigned char *outPixels, u_int canvasW, u_int canvasH, int options){
1967 boost::mutex::scoped_lock lock(this->m_mutex);
1968 #define PIXELIDX(x,y,w) ((y)*(w)*3+(x)*3)
1969 #define GETMAX(x,y) ((x)>(y)?(x):(y))
1970 #define OPTIONS_CHANNELS_MASK (LUX_HISTOGRAM_LOG-1)
1971 if (canvasW < 50 || canvasH < 25)
1973 const u_int borderW = 3; //size of the plot border in pixels
1974 const u_int guideW = 3; //size of the brightness guide bar in pixels
1975 const u_int plotH = canvasH - borderW - (guideW + 2) - (borderW - 1);
1976 const u_int plotW = canvasW - 2 * borderW;
1978 if (canvasW - 2 * borderW != m_bucketNr)
1979 m_newBucketNr = canvasW - 2 * borderW;
1981 //clear drawing area
1982 unsigned char color = 64;
1983 for (u_int x = 0; x < plotW; ++x) {
1984 for (u_int y = 0; y < plotH; ++y) {
1985 const u_int idx = PIXELIDX(x + borderW, y + borderW, canvasW);
1986 outPixels[idx] = color;
1987 outPixels[idx + 1] = color;
1988 outPixels[idx + 2] = color;
1992 //transform values to log if needed
1994 if (options & LUX_HISTOGRAM_LOG) {
1995 buckets = new float[m_bucketNr * 4];
1996 for (u_int i = 0; i < m_bucketNr * 4; ++i)
1997 buckets[i] = logf(1.f + m_buckets[i]);
1999 buckets = m_buckets;
2001 //draw histogram bars
2003 switch (options & OPTIONS_CHANNELS_MASK) {
2004 case LUX_HISTOGRAM_RGB: {
2005 //get maxima for scaling
2007 for (u_int i = 0; i < m_bucketNr * 4; ++i) {
2008 if (i % 4 != 3 && buckets[i] > max)
2013 for (u_int x = 0; x < plotW; ++x) {
2014 const u_int bucket = Clamp(x * m_bucketNr / (plotW - 1), 0U, m_bucketNr - 1);
2015 const float scale = plotH / max;
2016 for (u_int ch = 0; ch < 3; ++ch) {
2017 const u_int barHeight = Clamp(plotH - Round2UInt(buckets[bucket * 4 + ch] * scale), 0U, plotH);
2018 for(u_int y = barHeight; y < plotH; ++y)
2019 outPixels[PIXELIDX(x + borderW, y + borderW, canvasW) + ch] = 255;
2024 case LUX_HISTOGRAM_VALUE: channel++;
2025 case LUX_HISTOGRAM_BLUE: channel++;
2026 case LUX_HISTOGRAM_GREEN: channel++;
2027 case LUX_HISTOGRAM_RED: {
2028 //get maxima for scaling
2030 for (u_int i = 0; i < m_bucketNr; ++i) {
2031 if (buckets[i * 4 + channel] > max)
2032 max = buckets[i * 4 + channel];
2036 for (u_int x = 0; x < plotW; ++x) {
2037 const u_int bucket = Clamp(x * m_bucketNr / (plotW - 1), 0U, m_bucketNr - 1);
2038 const float scale = plotH / max;
2039 const u_int barHeight = Clamp(plotH - Round2UInt(buckets[bucket * 4 + channel] * scale), 0U, plotH);
2040 for(u_int y = barHeight; y < plotH; ++y) {
2041 const u_int idx = PIXELIDX(x + borderW, y + borderW, canvasW);
2042 outPixels[idx] = 255;
2043 outPixels[idx + 1] = 255;
2044 outPixels[idx + 2] = 255;
2050 case LUX_HISTOGRAM_RGB_ADD: {
2051 //calculate maxima for scaling
2053 for (u_int i = 0; i < m_bucketNr; ++i) {
2054 const float val = buckets[i * 4] + buckets[i * 4 + 1] + buckets[i * 4 + 2];
2060 for (u_int x = 0; x < plotW; ++x) {
2061 const u_int bucket = Clamp(x * m_bucketNr / (plotW - 1), 0U, m_bucketNr - 1);
2062 const float scale = plotH / max;
2063 u_int barHeight = Clamp(plotH - Round2UInt((buckets[bucket * 4] + buckets[bucket * 4 + 1] + buckets[bucket * 4 + 2]) * scale), 0U, plotH);
2064 u_int newHeight = barHeight;
2065 for (u_int ch = 0; ch < 3; ++ch) {
2066 newHeight += Floor2UInt(buckets[bucket * 4 + ch] * scale + 0.5f);
2067 for (u_int y = barHeight; y < newHeight; ++y)
2068 outPixels[PIXELIDX(x + borderW, y + borderW, canvasW) + ch] = 255;
2069 barHeight = newHeight;
2079 if (buckets != m_buckets)
2082 //draw brightness guide
2083 for (u_int x = 0; x < plotW; ++x) {
2084 u_int bucket = Clamp(x * m_bucketNr / (plotW - 1), 0U, m_bucketNr - 1);
2085 for (u_int y = plotH + 2; y < plotH + 2 + guideW; ++y) {
2086 const u_int idx = PIXELIDX(x + borderW, y + borderW, canvasW);
2087 const unsigned char color = static_cast<unsigned char>(Clamp(expf(bucket * m_bucketSize + m_lowRange) * 256.f, 0.f, 255.f)); //no need to gamma-correct, as we're already in display-gamma space
2088 switch (options & OPTIONS_CHANNELS_MASK) {
2089 case LUX_HISTOGRAM_RED:
2090 outPixels[idx] = color;
2091 outPixels[idx + 1] = 0;
2092 outPixels[idx + 2] = 0;
2094 case LUX_HISTOGRAM_GREEN:
2096 outPixels[idx + 1] = color;
2097 outPixels[idx + 2] = 0;
2099 case LUX_HISTOGRAM_BLUE:
2101 outPixels[idx + 1] = 0;
2102 outPixels[idx + 2] = color;
2105 outPixels[idx] = color;
2106 outPixels[idx + 1] = color;
2107 outPixels[idx + 2] = color;
2113 //draw EV zone markers
2114 for (u_int i = 0; i < 11; ++i) {
2126 const u_int bucket = m_zones[i];
2127 const u_int x = Clamp(bucket * plotW / (m_bucketNr - 1), 0U, plotW - 1);
2128 for (u_int y = plotH + 2; y < plotH + 2 + guideW; ++y) {
2129 const u_int idx = PIXELIDX(x + borderW, y + borderW, canvasW);
2130 outPixels[idx] = color;
2131 outPixels[idx + 1] = color;
2132 outPixels[idx + 2] = color;
2136 //draw zone boundaries on the plot
2137 for (u_int i = 0; i < 2; ++i) {
2141 bucket = m_zones[0];
2143 default: // In order to suppress a GCC warning
2145 bucket = m_zones[10];
2148 const u_int x = Clamp(bucket * plotW / (m_bucketNr - 1), 0U, plotW - 1);
2149 for (u_int y = 0; y < plotH; ++y) {
2150 const u_int idx = PIXELIDX(x + borderW, y + borderW, canvasW);
2151 if (outPixels[idx] == 255 &&
2152 outPixels[idx + 1] == 255 &&
2153 outPixels[idx + 2] == 255) {
2154 outPixels[idx] = 128;
2155 outPixels[idx + 1] = 128;
2156 outPixels[idx + 2] = 128;
2158 if (outPixels[idx] == 64)
2159 outPixels[idx] = 128;
2160 if (outPixels[idx + 1] == 64)
2161 outPixels[idx + 1] = 128;
2162 if (outPixels[idx + 2] == 64)
2163 outPixels[idx + 2] = 128;