David Bucciarelli avatar David Bucciarelli committed 854f789

Introduced Yangli Hector Yee's for convergence testing

Comments (0)

Files changed (8)

cmake/liblux.cmake

 
 SET(lux_films_src
 	film/fleximage.cpp
+	film/pdiff/LPyramid.cpp
+	film/pdiff/Metric.cpp
 	)
 SOURCE_GROUP("Source Files\\Films" FILES ${lux_films_src})
 
 #include "blackbodyspd.h"
 #include "osfunc.h"
 #include "streamio.h"
+#include "film/pdiff/Metric.h"
 
 #include <iostream>
 #include <fstream>
 	contribPool(NULL), filter(filt), filterTable(NULL), filterLUTs(NULL),
 	filename(filename1),
 	colorSpace(0.63f, 0.34f, 0.31f, 0.595f, 0.155f, 0.07f, 0.314275f, 0.329411f), // default is SMPTE
-	convergenceBufferReference(NULL), convergenceBufferReferenceCount(NULL),
-	varianceBuffer(NULL),
+	convergenceReference(NULL), varianceBuffer(NULL),
 	ZBuffer(NULL), use_Zbuf(useZbuffer),
 	debug_mode(debugmode), premultiplyAlpha(premult),
 	writeResumeFlm(w_resume_FLM), restartResumeFlm(restart_resume_FLM), writeFlmDirect(write_FLM_direct),
 	delete filterLUTs;
 	delete filter;
 	delete ZBuffer;
-	delete []convergenceBufferReference;
-	delete []convergenceBufferReferenceCount;
+	delete []convergenceReference;
 	delete varianceBuffer;
 	delete histogram;
 	delete contribPool;
 
 	// Allocate convergence buffers if needed
 	if (haltThreshold > 0.f) {
-		const u_int nPix = xPixelCount * yPixelCount;
-		convergenceBufferReference = new float[3 * nPix];
-
-		convergenceBufferReferenceCount = new float[nPix];
-		std::fill(convergenceBufferReferenceCount, convergenceBufferReferenceCount + nPix, 0.f);
-
-		convergenceBufferMap.resize(nPix, false);
-		convergencePixelCount = 0;
-
 		varianceBuffer = new VarianceBuffer(xPixelCount, yPixelCount);
 		varianceBuffer->Clear();
 	}
 }
 
 void Film::UpdateConvergenceInfo(const float *framebuffer) {
-	u_int pixelIndex = 0;
+	const u_int pixelCount = xPixelCount * yPixelCount;
+	if (!convergenceReference) {
+		// Check if we have at least 8 samples per pixel
+		bool missingSamples = false;
+		for (u_int yPixel = 0; !missingSamples && (yPixel < yPixelCount); ++yPixel) {
+			for (u_int xPixel = 0; xPixel < xPixelCount; ++xPixel) {
+				// Check if we have enough samples to evaluate convergence speed again
 
-	for (u_int yPixel = 0; yPixel < yPixelCount; ++yPixel) {
-		for (u_int xPixel = 0; xPixel < xPixelCount; ++xPixel, ++pixelIndex) {
-			// Check if we have enough samples to evaluate convergence speed again
-
-			// Merge all buffer results
-			float newSampleCount = 0.f;
-			for(u_int j = 0; j < bufferGroups.size(); ++j) {
-				if (!bufferGroups[j].enable)
-					continue;
-
-				for(u_int i = 0; i < bufferConfigs.size(); ++i) {
-					const Buffer &buffer = *(bufferGroups[j].buffers[i]);
-					if (!(bufferConfigs[i].output & BUF_FRAMEBUFFER))
+				// Merge all buffer results
+				float sampleCount = 0.f;
+				for(u_int j = 0; j < bufferGroups.size(); ++j) {
+					if (!bufferGroups[j].enable)
 						continue;
 
-					newSampleCount += buffer.pixels(xPixel, yPixel).weightSum;
+					for(u_int i = 0; i < bufferConfigs.size(); ++i) {
+						const Buffer &buffer = *(bufferGroups[j].buffers[i]);
+						if (!(bufferConfigs[i].output & BUF_FRAMEBUFFER))
+							continue;
+
+						sampleCount += buffer.pixels(xPixel, yPixel).weightSum;
+					}
 				}
-			}
 
-			float &oldSampleCount = convergenceBufferReferenceCount[pixelIndex];
-			if (newSampleCount - oldSampleCount > 8.f) {
-				// We have enough samples, update the convergence map
-				float newC[3];
-				newC[0] = Clamp(framebuffer[3 * pixelIndex], 0.f, 1.f);
-				newC[1] = Clamp(framebuffer[3 * pixelIndex + 1], 0.f, 1.f);
-				newC[2] = Clamp(framebuffer[3 * pixelIndex + 2], 0.f, 1.f);
-				float *oldC = &convergenceBufferReference[3 * pixelIndex];
-
-				const float newDelta = max(max(
-						fabs(newC[0] - oldC[0]),
-						fabs(newC[1] - oldC[1])),
-						fabs(newC[2] - oldC[2]));
-
-				// Update values
-				oldC[0] = newC[0];
-				oldC[1] = newC[1];
-				oldC[2] = newC[2];
-				oldSampleCount = newSampleCount;
-
-				if ((oldSampleCount > 0.f) && (newDelta <= haltThreshold)) {
-					// Convergence condition has been satisfied
-					if (!convergenceBufferMap[pixelIndex])
-						++convergencePixelCount;
-					convergenceBufferMap[pixelIndex] = true;
-				} else {
-					// Convergence condition has not been satisfied
-					if (convergenceBufferMap[pixelIndex])
-						--convergencePixelCount;
-					convergenceBufferMap[pixelIndex] = false;
+				if (sampleCount < 8.f) {
+					missingSamples = true;
+					break;
 				}
 			}
 		}
+
+		// Start the convergence test only if we have enough samples per pixel
+		if (!missingSamples) {
+			// Allocate the reference buffer and make a copy
+			convergenceReference = new float[3 * pixelCount];
+			std::copy(framebuffer, framebuffer + 3 * pixelCount, convergenceReference);
+
+			convergenceDiff.resize(pixelCount, false);
+		}
+	} else {
+		// Compare the new buffer with the old one
+		const u_int failedPixels = Yee_Compare(convergenceReference, framebuffer, convergenceDiff,
+				xPixelCount, yPixelCount);
+
+		// Make a copy for the new reference image
+		std::copy(framebuffer, framebuffer + 3 * pixelCount, convergenceReference);
+
+		// Check if we can stop the rendering
+		if (failedPixels == 0)
+			enoughSamplesPerPixel = true;
+
+		if (enoughSamplesPerPixel)
+			haltThresholdComplete = 1.f;
+		else
+			haltThresholdComplete = (pixelCount - failedPixels) / (float)pixelCount;
 	}
-
-	// Check if we can stop the rendering
-	const u_int pixelCount = xPixelCount * yPixelCount;
-	if (convergencePixelCount == pixelCount)
-		enoughSamplesPerPixel = true;
-
-	if (enoughSamplesPerPixel)
-		haltThresholdComplete = 1.f;
-	else
-		haltThresholdComplete = convergencePixelCount / (float)pixelCount;
 }
 
 void Film::AddTileSamples(const Contribution* const contribs, u_int num_contribs,
 	BlockedArray<VariancePixel> pixels;
 };
 
-
-/*struct VariancePixel {
-	VariancePixel() : Sn(0.f), mean(0.f), weightSum(0.f) { }
-
-	RGBColor Sn, mean, weightSum;
-};
-
-class VarianceBuffer {
-public:
-	VarianceBuffer(u_int x, u_int y) : pixels(x, y) {
-	}
-
-	~VarianceBuffer() { }
-
-	void Add(u_int x, u_int y, RGBColor v, float wt) {
-		if (wt == 0.f)
-			return;
-
-		VariancePixel &pixel = pixels(x, y);
-
-		const RGBColor weight(wt);
-		const RGBColor newWeightSum = pixel.weightSum + weight;
-
-		// Incremental computation of weighted mean:
-		// mean_n = mean_n-1 + (weight_n / weightSum_n)(x_n − mean_n−1 )
-		const RGBColor newMean = pixel.mean + (weight / newWeightSum) * (v - pixel.mean);
-
-		// Incremental computation of weighted variance:
-		//  S_n = S_n−1 + weight_n (x_n − mean_n−1)(x_n − mean_n)
-		//  Var = sqrt(S_n / weightSum_n)
-		const RGBColor newSn = pixel.Sn + wt * (v - pixel.mean) * (v - newMean);
-
-		pixel.Sn = newSn;
-		pixel.mean = newMean;
-		pixel.weightSum = newWeightSum;
-	}
-
-	void Clear() {
-		for (u_int y = 0; y < pixels.vSize(); ++y) {
-			for (u_int x = 0; x < pixels.uSize(); ++x) {
-				VariancePixel &pixel = pixels(x, y);
-				pixel.Sn = 0.f;
-				pixel.mean = 0.f;
-				pixel.weightSum = 0.f;
-			}
-		}
-	}
-
-	float GetVariance(u_int x, u_int y) const {
-		const VariancePixel &pixel = pixels(x, y);
-
-		// Var = sqrt(S_n / weightSum_n)
-		float result = 0.f;
-		if (pixel.weightSum.c[0] > 0.f)
-			result = max(result, sqrtf(fabs(pixel.Sn.c[0] / pixel.weightSum.c[0])));
-		if (pixel.weightSum.c[1] > 0.f)
-			result = max(result, sqrtf(fabs(pixel.Sn.c[1] / pixel.weightSum.c[1])));
-		if (pixel.weightSum.c[2] > 0.f)
-			result = max(result, sqrtf(fabs(pixel.Sn.c[2] / pixel.weightSum.c[2])));
-
-		return result;
-	}
-
-	BlockedArray<VariancePixel> pixels;
-};*/
-
 //------------------------------------------------------------------------------
 // Filter Look Up Table
 //------------------------------------------------------------------------------
 	std::vector<BufferConfig> bufferConfigs;
 	std::vector<BufferGroup> bufferGroups;
 
-	float *convergenceBufferReference;
-	float *convergenceBufferReferenceCount;
-	vector<bool> convergenceBufferMap;
-	u_int convergencePixelCount;
+	float *convergenceReference;
+	vector<bool> convergenceDiff;
 
 	VarianceBuffer *varianceBuffer;
 

film/fleximage.cpp

 						framebuffer[3 * i] = 255.f;
 						framebuffer[3 * i + 1] = framebuffer[3 * i + 2] = 0.f;
 					}*/
+					/*if (convergenceBufferReferenceCount[i] > 0.f)
+						framebuffer[3 * i] = framebuffer[3 * i + 1] = framebuffer[3 * i + 2] = convergenceDiff[i] ? 255 : 0;
+					else {
+						framebuffer[3 * i] = 255.f;
+						framebuffer[3 * i + 1] = framebuffer[3 * i + 2] = 0.f;
+					}*/
 				}
 					
 				// Some debug code used to show the variance

film/pdiff/LPyramid.cpp

+/*
+Laplacian Pyramid
+Copyright (C) 2006 Yangli Hector Yee
+
+This program is free software; you can redistribute it and/or modify it under the terms of the
+GNU General Public License as published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with this program;
+if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include "LPyramid.h"
+
+
+//////////////////////////////////////////////////////////////////////
+// Construction/Destruction
+//////////////////////////////////////////////////////////////////////
+
+LPyramid::LPyramid(float *image, int width, int height) :
+	Width(width),
+	Height(height)
+{
+	// Make the Laplacian pyramid by successively
+	// copying the earlier levels and blurring them
+	for (int i=0; i<MAX_PYR_LEVELS; i++) {
+		if (i == 0) {
+			Levels[i] = Copy(image);
+		} else {
+			Levels[i] = new float[Width * Height];
+			Convolve(Levels[i], Levels[i - 1]);
+		}
+	}
+}
+
+LPyramid::~LPyramid()
+{
+	for (int i=0; i<MAX_PYR_LEVELS; i++) {
+		if (Levels[i]) delete Levels[i];
+	}
+}
+
+float *LPyramid::Copy(float *img)
+{
+	int max = Width * Height;
+	float *out = new float[max];
+	for (int i = 0; i < max; i++) out[i] = img[i];
+	
+	return out;
+}
+
+void LPyramid::Convolve(float *a, float *b)
+// convolves image b with the filter kernel and stores it in a
+{
+	int y,x,i,j,nx,ny;
+	const float Kernel[] = {0.05f, 0.25f, 0.4f, 0.25f, 0.05f};
+
+	for (y=0; y<Height; y++) {
+		for (x=0; x<Width; x++) {
+			int index = y * Width + x;
+			a[index] = 0.0f;
+			for (i=-2; i<=2; i++) {
+				for (j=-2; j<=2; j++) {
+					nx=x+i;
+					ny=y+j;
+					if (nx<0) nx=-nx;
+					if (ny<0) ny=-ny;
+					if (nx>=Width) nx=2*Width-nx-1;
+					if (ny>=Height) ny=2*Height-ny-1;
+					a[index] += Kernel[i+2] * Kernel[j+2] * b[ny * Width + nx];
+				} 
+			}
+		}
+	}
+}
+
+float LPyramid::Get_Value(int x, int y, int level)
+{
+	int index = x + y * Width;
+	int l = level;
+	if (l > MAX_PYR_LEVELS) l = MAX_PYR_LEVELS;
+	return Levels[level][index];
+}
+
+

film/pdiff/LPyramid.h

+/*
+Laplacian Pyramid
+Copyright (C) 2006 Yangli Hector Yee
+
+This program is free software; you can redistribute it and/or modify it under the terms of the
+GNU General Public License as published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with this program;
+if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+#ifndef _LPYRAMID_H
+#define _LPYRAMID_H
+
+#define MAX_PYR_LEVELS 8
+
+class LPyramid
+{
+public:	
+	LPyramid(float *image, int width, int height);
+	virtual ~LPyramid();
+	float Get_Value(int x, int y, int level);
+protected:
+	float *Copy(float *img);
+	void Convolve(float *a, float *b);
+	
+	// Succesively blurred versions of the original image
+	float *Levels[MAX_PYR_LEVELS];
+
+	int Width;
+	int Height;
+};
+
+#endif // _LPYRAMID_H
+

film/pdiff/Metric.cpp

+/*
+Metric
+Copyright (C) 2006 Yangli Hector Yee
+
+This program is free software; you can redistribute it and/or modify it under the terms of the
+GNU General Public License as published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with this program;
+if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+// Adapted for LuxRender by Dade
+
+#include <cstdio>
+#include "Metric.h"
+#include "LPyramid.h"
+#include <math.h>
+
+#ifndef M_PI
+#define M_PI 3.14159265f
+#endif
+
+/*
+* Given the adaptation luminance, this function returns the
+* threshold of visibility in cd per m^2
+* TVI means Threshold vs Intensity function
+* This version comes from Ward Larson Siggraph 1997
+*/ 
+
+float tvi(float adaptation_luminance)
+{
+      // returns the threshold luminance given the adaptation luminance
+      // units are candelas per meter squared
+
+      float log_a, r, result; 
+      log_a = log10f(adaptation_luminance);
+
+      if (log_a < -3.94f) {
+            r = -2.86f;
+      } else if (log_a < -1.44f) {
+            r = powf(0.405f * log_a + 1.6f , 2.18f) - 2.86f;
+      } else if (log_a < -0.0184f) {
+            r = log_a - 0.395f;
+      } else if (log_a < 1.9f) {
+            r = powf(0.249f * log_a + 0.65f, 2.7f) - 0.72f;
+      } else {
+            r = log_a - 1.255f;
+      }
+
+      result = powf(10.0f , r); 
+
+      return result;
+
+} 
+
+// computes the contrast sensitivity function (Barten SPIE 1989)
+// given the cycles per degree (cpd) and luminance (lum)
+float csf(float cpd, float lum)
+{
+	float a, b, result; 
+	
+	a = 440.0f * powf((1.0f + 0.7f / lum), -0.2f);
+	b = 0.3f * powf((1.0f + 100.0f / lum), 0.15f);
+		
+	result = a * cpd * expf(-b * cpd) * sqrtf(1.0f + 0.06f * expf(b * cpd)); 
+	
+	return result;	
+}
+
+/*
+* Visual Masking Function
+* from Daly 1993
+*/
+float mask(float contrast)
+{
+      float a, b, result;
+      a = powf(392.498f * contrast,  0.7f);
+      b = powf(0.0153f * a, 4.0f);
+      result = powf(1.0f + b, 0.25f); 
+
+      return result;
+} 
+
+// convert Adobe RGB (1998) with reference white D65 to XYZ
+void AdobeRGBToXYZ(float r, float g, float b, float &x, float &y, float &z)
+{
+	// matrix is from http://www.brucelindbloom.com/
+	x = r * 0.576700f + g * 0.185556f + b * 0.188212f;
+	y = r * 0.297361f + g * 0.627355f + b * 0.0752847f;
+	z = r * 0.0270328f + g * 0.0706879f + b * 0.991248f;
+}
+
+void XYZToLAB(float x, float y, float z, float &L, float &A, float &B)
+{
+	static float xw = -1;
+	static float yw;
+	static float zw;
+	// reference white
+	if (xw < 0) {
+		AdobeRGBToXYZ(1, 1, 1, xw, yw, zw);
+	}
+	const float epsilon  = 216.0f / 24389.0f;
+	const float kappa = 24389.0f / 27.0f;
+	float f[3];
+	float r[3];
+	r[0] = x / xw;
+	r[1] = y / yw;
+	r[2] = z / zw;
+	for (int i = 0; i < 3; i++) {
+		if (r[i] > epsilon) {
+			f[i] = powf(r[i], 1.0f / 3.0f);
+		} else {
+			f[i] = (kappa * r[i] + 16.0f) / 116.0f;
+		}
+	}
+	L = 116.0f * f[1] - 16.0f;
+	A = 500.0f * (f[0] - f[1]);
+	B = 200.0f * (f[1] - f[2]);
+}
+
+unsigned int Yee_Compare(
+		const float *rgbA,
+		const float *rgbB,
+		std::vector<bool> &diff,
+		const unsigned int width,
+		const unsigned int height,
+		const bool LuminanceOnly,
+		const float FieldOfView,
+		const float Gamma,
+		const float Luminance,
+		const float ColorFactor,
+		const unsigned int DownSample)
+{
+	unsigned int i, dim;
+	dim = width * height;
+	bool identical = true;
+	for (i = 0; i < 3 * dim; i++) {
+		if (rgbA[i] != rgbB[i]) {
+		  identical = false;
+		  break;
+		}
+	}
+	if (identical) {
+		// Images are binary identical
+		return true;
+	}
+	
+	// assuming colorspaces are in Adobe RGB (1998) convert to XYZ
+	float *aX = new float[dim];
+	float *aY = new float[dim];
+	float *aZ = new float[dim];
+	float *bX = new float[dim];
+	float *bY = new float[dim];
+	float *bZ = new float[dim];
+	float *aLum = new float[dim];
+	float *bLum = new float[dim];
+	
+	float *aA = new float[dim];
+	float *bA = new float[dim];
+	float *aB = new float[dim];
+	float *bB = new float[dim];
+	
+	unsigned int x, y;
+	for (y = 0; y < height; y++) {
+		for (x = 0; x < width; x++) {
+			float r, g, b, l;
+			i = x + y * width;
+			r = powf(rgbA[3 * i], Gamma);
+			g = powf(rgbA[3 * i + 1], Gamma);
+			b = powf(rgbA[3 * i + 2], Gamma);						
+			AdobeRGBToXYZ(r,g,b,aX[i],aY[i],aZ[i]);			
+			XYZToLAB(aX[i], aY[i], aZ[i], l, aA[i], aB[i]);
+			r = powf(rgbB[3 * i], Gamma);
+			g = powf(rgbB[3 * i + 1], Gamma);
+			b = powf(rgbB[3 * i + 2], Gamma);						
+			AdobeRGBToXYZ(r,g,b,bX[i],bY[i],bZ[i]);
+			XYZToLAB(bX[i], bY[i], bZ[i], l, bA[i], bB[i]);
+			aLum[i] = aY[i] * Luminance;
+			bLum[i] = bY[i] * Luminance;
+		}
+	}
+
+	// Constructing Laplacian Pyramids
+	
+	LPyramid *la = new LPyramid(aLum, width, height);
+	LPyramid *lb = new LPyramid(bLum, width, height);
+	
+	float num_one_degree_pixels = (float) (2 * tan(FieldOfView * 0.5 * M_PI / 180) * 180 / M_PI);
+	float pixels_per_degree = width / num_one_degree_pixels;
+	
+	// Performing test
+	
+	float num_pixels = 1;
+	unsigned int adaptation_level = 0;
+	for (i = 0; i < MAX_PYR_LEVELS; i++) {
+		adaptation_level = i;
+		if (num_pixels > num_one_degree_pixels) break;
+		num_pixels *= 2;
+	}
+	
+	float cpd[MAX_PYR_LEVELS];
+	cpd[0] = 0.5f * pixels_per_degree;
+	for (i = 1; i < MAX_PYR_LEVELS; i++) cpd[i] = 0.5f * cpd[i - 1];
+	float csf_max = csf(3.248f, 100.0f);
+	
+	float F_freq[MAX_PYR_LEVELS - 2];
+	for (i = 0; i < MAX_PYR_LEVELS - 2; i++) F_freq[i] = csf_max / csf( cpd[i], 100.0f);
+	
+	unsigned int pixels_failed = 0;
+	for (y = 0; y < height; y++) {
+	  for (x = 0; x < width; x++) {
+		int index = x + y * width;
+		float contrast[MAX_PYR_LEVELS - 2];
+		float sum_contrast = 0;
+		for (i = 0; i < MAX_PYR_LEVELS - 2; i++) {
+			float n1 = fabsf(la->Get_Value(x,y,i) - la->Get_Value(x,y,i + 1));
+			float n2 = fabsf(lb->Get_Value(x,y,i) - lb->Get_Value(x,y,i + 1));
+			float numerator = (n1 > n2) ? n1 : n2;
+			float d1 = fabsf(la->Get_Value(x,y,i+2));
+			float d2 = fabsf(lb->Get_Value(x,y,i+2));
+			float denominator = (d1 > d2) ? d1 : d2;
+			if (denominator < 1e-5f) denominator = 1e-5f;
+			contrast[i] = numerator / denominator;
+			sum_contrast += contrast[i];
+		}
+		if (sum_contrast < 1e-5) sum_contrast = 1e-5f;
+		float F_mask[MAX_PYR_LEVELS - 2];
+		float adapt = la->Get_Value(x,y,adaptation_level) + lb->Get_Value(x,y,adaptation_level);
+		adapt *= 0.5f;
+		if (adapt < 1e-5) adapt = 1e-5f;
+		for (i = 0; i < MAX_PYR_LEVELS - 2; i++) {
+			F_mask[i] = mask(contrast[i] * csf(cpd[i], adapt)); 
+		}
+		float factor = 0;
+		for (i = 0; i < MAX_PYR_LEVELS - 2; i++) {
+			factor += contrast[i] * F_freq[i] * F_mask[i] / sum_contrast;
+		}
+		if (factor < 1) factor = 1;
+		if (factor > 10) factor = 10;
+		float delta = fabsf(la->Get_Value(x,y,0) - lb->Get_Value(x,y,0));
+		bool pass = true;
+		// pure luminance test
+		if (delta > factor * tvi(adapt)) {
+			pass = false;
+		} else if (!LuminanceOnly) {
+			// CIE delta E test with modifications
+			float color_scale = ColorFactor;
+			// ramp down the color test in scotopic regions
+			if (adapt < 10.0f) {
+                          // Don't do color test at all.
+                          color_scale = 0.0;
+			}
+			float da = aA[index] - bA[index];
+			float db = aB[index] - bB[index];
+			da = da * da;
+			db = db * db;
+			float delta_e = (da + db) * color_scale;
+			if (delta_e > factor) {
+				pass = false;
+			}
+		}
+		if (!pass) {
+			pixels_failed++;
+			diff[index] = false;
+		} else
+			diff[index] = true;
+	  }
+	}
+	
+	if (aX) delete[] aX;
+	if (aY) delete[] aY;
+	if (aZ) delete[] aZ;
+	if (bX) delete[] bX;
+	if (bY) delete[] bY;
+	if (bZ) delete[] bZ;
+	if (aLum) delete[] aLum;
+	if (bLum) delete[] bLum;
+	if (la) delete la;
+	if (lb) delete lb;
+	if (aA) delete aA;
+	if (bA) delete bA;
+	if (aB) delete aB;
+	if (bB) delete bB;
+	
+	return pixels_failed;
+}

film/pdiff/Metric.h

+/*
+Metric
+Copyright (C) 2006 Yangli Hector Yee
+
+This program is free software; you can redistribute it and/or modify it under the terms of the
+GNU General Public License as published by the Free Software Foundation; either version 2 of the License,
+or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with this program;
+if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#ifndef _METRIC_H
+#define _METRIC_H
+
+#include <vector>
+
+// Image comparison metric using Yee's method
+// References: A Perceptual Metric for Production Testing, Hector Yee, Journal of Graphics Tools 2004
+unsigned int Yee_Compare(
+		const float *rgbA,
+		const float *rgbB,
+		std::vector<bool> &diff,
+		const unsigned int width,
+		const unsigned int height,
+		const bool LuminanceOnly = false,
+		const float FieldOfView = 45.f,
+		const float Gamma = 2.2f,
+		const float Luminance = 100.f,
+		const float ColorFactor = 1.f,
+		const unsigned int DownSample = 0);
+
+#endif
+
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.