
/*
 * pbrt source code Copyright(c) 1998-2004 Matt Pharr and Greg Humphreys
 *
 * All Rights Reserved.
 * For educational use only; commercial use expressly forbidden.
 * NO WARRANTY, express or implied, for this software.
 * (See file License.txt for complete license)
 */

// image.cpp*
#include "pbrt.h"
#include "film.h"
#include "color.h"
#include "paramset.h"
#include "tonemap.h"
#include "sampling.h"

#include <stdio.h>
// TgaFilm Declarations
class TgaFilm : public Film {
public:
	// TgaFilm Public Methods
	TgaFilm::TgaFilm(int xres, int yres,
	                     Filter *filt, const float crop[4],
			             const string &filename, bool premult, int wf,
						 const string &toneMapper, float bloomWidth, float bloomRadius, float gamma, float dither);
	~TgaFilm() {
		delete pixels;
		delete filter;
		delete[] filterTable;
	}
	void AddSample(const Sample &sample, const Ray &ray,
	               const Spectrum &L, float alpha);
	void GetSampleExtent(int *xstart, int *xend,
	                     int *ystart, int *yend) const;
	void WriteImage();
private:
	// TgaFilm Private Data
	Filter *filter;
	int writeFrequency, sampleCount;
	string filename;
	bool premultiplyAlpha;
	float cropWindow[4];
	int xPixelStart, yPixelStart, xPixelCount, yPixelCount;
	
	string toneMapper;
	float bloomWidth, bloomRadius, gamma, dither;
	
	struct Pixel {
		Pixel() : L(0.f) {
			alpha = 0.f;
			weightSum = 0.f;
		}
		Spectrum L;
		float alpha, weightSum;
	};
	BlockedArray<Pixel> *pixels;
	float *filterTable;
};
// TgaFilm Method Definitions
TgaFilm::TgaFilm(int xres, int yres,
                     Filter *filt, const float crop[4],
		             const string &fn, bool premult, int wf,
					 const string &tm, float bw, float br, float g, float d)
	: Film(xres, yres) {
	filter = filt;
	memcpy(cropWindow, crop, 4 * sizeof(float));
	filename = fn;
	premultiplyAlpha = premult;
	writeFrequency = sampleCount = wf;
	// Compute film image extent
	xPixelStart = Ceil2Int(xResolution * cropWindow[0]);
	xPixelCount =
		Ceil2Int(xResolution * cropWindow[1]) - xPixelStart;
	yPixelStart =
		Ceil2Int(yResolution * cropWindow[2]);
	yPixelCount =
		Ceil2Int(yResolution * cropWindow[3]) - yPixelStart;
	// Allocate film image storage
	pixels = new BlockedArray<Pixel>(xPixelCount, yPixelCount);
	// Precompute filter weight table
	#define FILTER_TABLE_SIZE 16
	filterTable =
		new float[FILTER_TABLE_SIZE * FILTER_TABLE_SIZE];
	float *ftp = filterTable;
	for (int y = 0; y < FILTER_TABLE_SIZE; ++y) {
		float fy = ((float)y + .5f) * filter->yWidth /
			FILTER_TABLE_SIZE;
		for (int x = 0; x < FILTER_TABLE_SIZE; ++x) {
			float fx = ((float)x + .5f) * filter->xWidth /
				FILTER_TABLE_SIZE;
			*ftp++ = filter->Evaluate(fx, fy);
		}
	}
	
	toneMapper = tm;
	bloomWidth = bw; bloomRadius = br;
	gamma = g; dither = d;	
}
void TgaFilm::AddSample(const Sample &sample,
		const Ray &ray, const Spectrum &L, float alpha) {
	// Compute sample's raster extent
	float dImageX = sample.imageX - 0.5f;
	float dImageY = sample.imageY - 0.5f;
	int x0 = Ceil2Int (dImageX - filter->xWidth);
	int x1 = Floor2Int(dImageX + filter->xWidth);
	int y0 = Ceil2Int (dImageY - filter->yWidth);
	int y1 = Floor2Int(dImageY + filter->yWidth);
	x0 = max(x0, xPixelStart);
	x1 = min(x1, xPixelStart + xPixelCount - 1);
	y0 = max(y0, yPixelStart);
	y1 = min(y1, yPixelStart + yPixelCount - 1);
	// Loop over filter support and add sample to pixel arrays
	// Precompute $x$ and $y$ filter table offsets
	int *ifx = (int *)alloca((x1-x0+1) * sizeof(int));
	for (int x = x0; x <= x1; ++x) {
		float fx = fabsf((x - dImageX) *
		                 filter->invXWidth * FILTER_TABLE_SIZE);
		ifx[x-x0] = min(Floor2Int(fx), FILTER_TABLE_SIZE-1);
	}
	int *ify = (int *)alloca((y1-y0+1) * sizeof(int));
	for (int y = y0; y <= y1; ++y) {
		float fy = fabsf((y - dImageY) *
		                 filter->invYWidth * FILTER_TABLE_SIZE);
		ify[y-y0] = min(Floor2Int(fy), FILTER_TABLE_SIZE-1);
	}
	for (int y = y0; y <= y1; ++y)
		for (int x = x0; x <= x1; ++x) {
			// Evaluate filter value at $(x,y)$ pixel
			int offset = ify[y-y0]*FILTER_TABLE_SIZE + ifx[x-x0];
			float filterWt = filterTable[offset];
			// Update pixel values with filtered sample contribution
			Pixel &pixel = (*pixels)(x - xPixelStart, y - yPixelStart);
			pixel.L.AddWeighted(filterWt, L);
			pixel.alpha += alpha * filterWt;
			pixel.weightSum += filterWt;
		}
	// Possibly write out in-progress image
	if (--sampleCount == 0) {
		WriteImage();
		sampleCount = writeFrequency;
	}
}
void TgaFilm::GetSampleExtent(int *xstart,
		int *xend, int *ystart, int *yend) const {
	*xstart = Floor2Int(xPixelStart + .5f - filter->xWidth);
	*xend   = Floor2Int(xPixelStart + .5f + xPixelCount  +
		filter->xWidth);
	*ystart = Floor2Int(yPixelStart + .5f - filter->yWidth);
	*yend   = Floor2Int(yPixelStart + .5f + yPixelCount +
		filter->yWidth);
}
void TgaFilm::WriteImage() {
	// Convert image to RGB and compute final pixel values
	int nPix = xPixelCount * yPixelCount;
	float *rgb = new float[3*nPix], *alpha = new float[nPix];
	int offset = 0;
	for (int y = 0; y < yPixelCount; ++y) {
		for (int x = 0; x < xPixelCount; ++x) {
			// Convert pixel spectral radiance to RGB
			float xyz[3];
			(*pixels)(x, y).L.XYZ(xyz);
			const float
				rWeight[3] = { 3.240479f, -1.537150f, -0.498535f };
			const float
				gWeight[3] = {-0.969256f,  1.875991f,  0.041556f };
			const float
				bWeight[3] = { 0.055648f, -0.204043f,  1.057311f };
			rgb[3*offset  ] = rWeight[0]*xyz[0] +
			                  rWeight[1]*xyz[1] +
				              rWeight[2]*xyz[2];
			rgb[3*offset+1] = gWeight[0]*xyz[0] +
			                  gWeight[1]*xyz[1] +
				              gWeight[2]*xyz[2];
			rgb[3*offset+2] = bWeight[0]*xyz[0] +
			                  bWeight[1]*xyz[1] +
				              bWeight[2]*xyz[2];
			alpha[offset] = (*pixels)(x, y).alpha;
			// Normalize pixel with weight sum
			float weightSum = (*pixels)(x, y).weightSum;
			if (weightSum != 0.f) {
				float invWt = 1.f / weightSum;
				rgb[3*offset  ] =
					Clamp(rgb[3*offset  ] * invWt, 0.f, INFINITY);
				rgb[3*offset+1] =
					Clamp(rgb[3*offset+1] * invWt, 0.f, INFINITY);
				rgb[3*offset+2] =
					Clamp(rgb[3*offset+2] * invWt, 0.f, INFINITY);
				alpha[offset] = Clamp(alpha[offset] * invWt, 0.f, 1.f);
			}
			// Compute premultiplied alpha color
			if (premultiplyAlpha) {
				rgb[3*offset  ] *= alpha[offset];
				rgb[3*offset+1] *= alpha[offset];
				rgb[3*offset+2] *= alpha[offset];
			}
			++offset;
		}
	}
	
	//// CHANGES START HERE! ////
	// apply the tone mapper and such
	ParamSet toneParams;
	ApplyImagingPipeline(rgb,xPixelCount,yPixelCount,NULL,bloomRadius,bloomWidth,toneMapper.c_str(),&toneParams,gamma,dither,255);
	
	// All following code deals with the particulars of creating a targa file
	// cross platform
	// open the targa file for output
	FILE* tgaFile = fopen(filename.c_str(),"wb");
	if (!tgaFile) {
		std::cout << "Error: Cannot open file for output" << std::endl;
		delete[] alpha;
		delete[] rgb;
		return;	
	}
	
	// write the header
	// make sure its platform independent of little endian and big endian
	char header[18];
	memset(header, 0,sizeof(char)*18);
	
	header[2] = 2;							// set the data type of the targa (2 = uncompressed)
	short xResShort = xResolution;			// set the resolution and make sure the bytes are in the right order
	header[13] = (char) (xResShort >> 8);
	header[12] = xResShort;	
	short yResShort = yResolution;
	header[15] = (char) (yResShort >> 8);
	header[14] = yResShort;
	header[16] = 32;						// set the bitdepth
	
	// put the header data into the file
	for (int i=0; i < 18; i++)
		fputc(header[i],tgaFile);
	
	// write the bytes of data out
	for (int i=yPixelCount-1;  i >= 0 ; i--) {
		for (int j=0;  j < xPixelCount; j++) {	
			fputc((int) rgb[(i*xPixelCount+j)*3+2], tgaFile);
			fputc((int) rgb[(i*xPixelCount+j)*3+1], tgaFile);
			fputc((int) rgb[(i*xPixelCount+j)*3+0], tgaFile);
			fputc((int) (255.0*alpha[(i*xPixelCount+j)]), tgaFile);
		}
	}
		
	fclose(tgaFile);

	delete[] alpha;
	delete[] rgb;
}
extern "C" DLLEXPORT Film *CreateFilm(const ParamSet &params, Filter *filter)
{
	string filename = params.FindOneString("filename", "pbrt.tga");
	bool premultiplyAlpha = params.FindOneBool("premultiplyalpha", true);	
	int xres = params.FindOneInt("xresolution", 640);
	int yres = params.FindOneInt("yresolution", 480);
	float crop[4] = { 0, 1, 0, 1 };
	int cwi;
	const float *cr = params.FindFloat("cropwindow", &cwi);
	if (cr && cwi == 4) {
		crop[0] = Clamp(min(cr[0], cr[1]), 0., 1.);
		crop[1] = Clamp(max(cr[0], cr[1]), 0., 1.);
		crop[2] = Clamp(min(cr[2], cr[3]), 0., 1.);
		crop[3] = Clamp(max(cr[2], cr[3]), 0., 1.);
	}
	int writeFrequency = params.FindOneInt("writefrequency", -1);

	// new variables
	string toneMapper = params.FindOneString("tonemapper", "maxwhite");
	float bloomWidth = params.FindOneFloat("bloomwidth", 0.0f);
	float bloomRadius = params.FindOneFloat("bloomradius", 0.0f);
	float gamma = params.FindOneFloat("gamma", 1.0f);
	float dither = params.FindOneFloat("dither", 0.0f);

	return new TgaFilm(xres, yres, filter, crop,
		filename, premultiplyAlpha, writeFrequency,
		toneMapper, bloomWidth, bloomRadius, gamma, dither);
}
