/****************************************************************************
 *                  hdr.cpp
 *
 *  Author: Mael, Christoph Hormann
 *
 *  High Dynamic Range Images reading/writing function
 *
 *
 *  Writing code derived from PPM code
 *
 *  based on RADIANCE HDR image specs:
 *
 *    http://radsite.lbl.gov/radiance/refer/Notes/picture_format.html
 *
 * from Persistence of Vision(tm) Ray Tracer version 3.6.
 * Copyright 1991-2003 Persistence of Vision Team
 * Copyright 2003-2004 Persistence of Vision Raytracer Pty. Ltd.
 *---------------------------------------------------------------------------
 * NOTICE: This source code file is provided so that users may experiment
 * with enhancements to POV-Ray and to port the software to platforms other
 * than those supported by the POV-Ray developers. There are strict rules
 * regarding how you are permitted to use this file. These rules are contained
 * in the distribution and derivative versions licenses which should have been
 * provided with this file.
 *
 * These licences may be found online, linked from the end-user license
 * agreement that is located at http://www.povray.org/povlegal.html
 *---------------------------------------------------------------------------
 * This program is based on the popular DKB raytracer version 2.12.
 * DKBTrace was originally written by David K. Buck.
 * DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins.
 *---------------------------------------------------------------------------
 *
 *===========================================================================
 * This file is part of MegaPOV, a modified and unofficial version of POV-Ray
 * For more information on MegaPOV visit our website:
 * http://megapov.inetart.net/
 *===========================================================================
 *
 * $RCSfile: hdr.cpp,v $
 * $Revision: 1.16 $
 * $Author: chris $
 *
 *****************************************************************************/ 
 
#include "frame.h"
#include "povray.h"
#include "optout.h"
#include "hdr.h"
#include "pov_util.h"
#include "povmsend.h"
#include "colour.h"

#include <algorithm>

#include <stdio.h>
#include <stdlib.h>
#include <math.h>

/*ML: hdr_patch*/
#ifdef HDR_PATCH
BEGIN_POV_NAMESPACE
/*****************************************************************************
* Local preprocessor defines
******************************************************************************/

#define HDR_DEBUG 0

#define  MINELEN       8       /* minimum scanline length for encoding */
#define  MAXELEN       0x7fff  /* maximum scanline length for encoding */
#define  MINRUN        4       /* minimum run length */

/*****************************************************************************
* Local functions
******************************************************************************/

int Read_Old_Line (unsigned char **original, int width, IStream *f);
void RGBE_To_ImgLine(unsigned char **original, IMAGE_FLOAT_LINE *img_line, int width);
void RGBE_To_ColorLine(unsigned char **original, COLOUR *Col_Line, int width);
/*****************************************************************************
*
* FUNCTION
*
*    Read_Old_Line
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
* DESCRIPTION
*
*    read an old style line from HDR Image
*
* CHANGES
*
*    March 2003 - Creation based on RADIANCE hdr reading code
*
******************************************************************************/

int Read_Old_Line (unsigned char *orig_image, int width, IStream *f)
{
	int  rshift = 0;
	int  i;

#if (HDR_DEBUG == 1)
	Debug_Info("reading old style HDR line...\n");
#endif

	while (width > 0)
	{
		orig_image[0] = f->Read_Byte();
		orig_image[1] = f->Read_Byte();
		orig_image[2] = f->Read_Byte();
		if ((i = f->Read_Byte()) == EOF) return 0;
		orig_image[3] = i;

		if ((orig_image[0] == 1) && (orig_image[1] == 1) && (orig_image[2] == 1))
		{
			for (i = orig_image[3] << rshift; i > 0; i--)
			{
				POV_MEMCPY(orig_image, orig_image-4, 4);
				orig_image += 4;
				width--;
			}
			rshift += 8;
		}
		else
		{
			orig_image += 4;
			width--;
			rshift = 0;
		}
	}

  return 1;
}

/*****************************************************************************
*
* FUNCTION
*
*    RGBE_To_ImgLine
*
* INPUT
*
*    original : RGBE data line
*    width : length of line
*
* OUTPUT
*
*    img_line : line of the image to fill
*
* RETURNS
*
* AUTHOR
*
* DESCRIPTION
*
*    convert an RGBE line into IMAGE_FLOAT_LINE
*
* CHANGES
*
*    March 2003 - Creation based on RADIANCE hdr reading code
*
******************************************************************************/

void RGBE_To_ImgLine(unsigned char *orig_image, IMAGE_FLOAT_LINE *img_line, int width)
{
	double v;

	for (int j = 0; j < width; j++)
	{
		v = ldexp(1.0, (int) orig_image[3] - (128+8));
		img_line->red[j] = (double(orig_image[0]) + 0.5) * v;
		img_line->green[j] = (double(orig_image[1]) + 0.5) * v;
		img_line->blue[j] = (double(orig_image[2]) + 0.5) * v;

		orig_image += 4;
	}
}

/*****************************************************************************
*
* FUNCTION
*
*    RGBE_To_ColorLine
*
* INPUT
*
*    original : RGBE data line
*    width : length of line
*
* OUTPUT
*
*    Col_Line : COLOUR  array of size 'width'
*
* RETURNS
*
* AUTHOR
*
* DESCRIPTION
*
*    convert an RGBE line into COLOUR array
*
* CHANGES
*
*    March 2003 - Creation based on RADIANCE hdr reading code
*
******************************************************************************/

void RGBE_To_ColorLine(unsigned char *orig_image, COLOUR *Col_Line, int width)
{
	double v;

	for (int j = 0; j < width; j++)
	{
		v = ldexp(1.0, (int) orig_image[3] - (128+8));
		Col_Line[j][pRED] = (double(orig_image[0]) + 0.5) * v;
		Col_Line[j][pGREEN] = (double(orig_image[1]) + 0.5) * v;
		Col_Line[j][pBLUE] = (double(orig_image[2]) + 0.5) * v;

		orig_image += 4;
	}
}

/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

HDR_Image::HDR_Image(char *name, int w, int h, int m, int l)
{
	mode = m;
	filename = name;
	in_file = NULL;
	out_file = NULL;
	width = w;
	height = h;
	line_number = l;

	char line[BUFSIZ];
	char s1[3], s2[3];
	int i;

	switch (mode)
	{
		case READ_MODE:

			// We can't resume from stdout. 
			if (opts.Options & TO_STDOUT || (in_file = New_Checked_IStream(name, POV_File_Image_HDR)) == NULL)
			{
				return;
			}

			// Header
			do 
			{
				in_file->getline(line, BUFSIZ);
			}
			while (!in_file->eof() && (line[0] != '-') && (line[0] != '+'));

			// Image size
			if (sscanf(line, "%2[+-XY] %d %2[+-XY] %d\n", s1, &h, s2, &w) != 4)
				Error ("Bad HDR header");

			Send_Progress("Resuming interrupted trace", PROGRESS_RESUMING_INTERRUPTED_TRACE);

			width = w;
			height = h;

			break;

		case WRITE_MODE:

			if (opts.Options & TO_STDOUT)
			{
				out_file = New_OStream("stdout", POV_File_Image_HDR, false);
			}
			else if ((out_file = New_Checked_OStream(name, POV_File_Image_HDR, false)) == NULL)
			{
				return;
			}

			out_file->printf("#?RADIANCE\n");
			out_file->printf("SOFTWARE= %s\n", POV_RAY_VERSION);
			out_file->printf("FORMAT=32-bit_rle_rgbe\n");

			out_file->printf("\n-Y %d +X %d\n", h, w);

			width = w;
			height = h;

			break;

		case APPEND_MODE:

			if (opts.Options & TO_STDOUT)
			{
				out_file = New_OStream("stdout", POV_File_Image_HDR, true);
			}
			else if ((out_file = New_Checked_OStream(name, POV_File_Image_HDR, true)) == NULL)
			{
				return;
			}

			break;
	}

	valid = true;
}

/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

HDR_Image::~HDR_Image()
{
  // Close the input file (if open) 
  if(in_file != NULL)
    delete in_file;

  // Close the output file (if open) 
  if(out_file != NULL)
  {
    out_file->flush();
    delete out_file;
  }

  in_file = NULL;
  out_file = NULL;
}

/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

void HDR_Image::Write_Line(COLOUR *line_data)
{
	int  i, j, beg, cnt = 1;
	int  c2;
	RGBE color_rgbe;
	RGBE *line_rgbe;

	if(valid == false)
		Error("Cannot access output image file.");

	if (width < MINELEN | width > MAXELEN)
	{
		for (j = 0; j < width; j++)
		{
			ColorToRGBE(color_rgbe,
									line_data[j][pRED],
									line_data[j][pGREEN],
									line_data[j][pBLUE]);

			if (!out_file->write (&color_rgbe, sizeof(RGBE)))
				Error("Error writing HDR output data to %s.",filename);
		}
	}
	else
	{
		out_file->Write_Byte(2);                    /* put magic header */
		out_file->Write_Byte(2);
		out_file->Write_Byte(width>>8);
		out_file->Write_Byte(width&255);

		line_rgbe = (RGBE *)POV_MALLOC(width * sizeof(RGBE), "HDR image line");

		for (j = 0; j < width; j++)                 /* convert pixels */
		{
			ColorToRGBE(line_rgbe[j],
									line_data[j][pRED],
									line_data[j][pGREEN],
									line_data[j][pBLUE]);
		}
		                                            /* put components seperately */
		for (i = 0; i < 4; i++)
		{
			for (j = 0; j < width; j += cnt)
			{                                         /* find next run */
				for (beg = j; beg < width; beg += cnt)
				{
					for (cnt = 1; cnt < 127 && beg+cnt < width &&
								 line_rgbe[beg+cnt][i] == line_rgbe[beg][i]; cnt++) ;
					if (cnt >= MINRUN) break;             /* long enough */
				}
				if (beg-j > 1 && beg-j < MINRUN)
				{
					c2 = j+1;
					while (line_rgbe[c2++][i] == line_rgbe[j][i])
						if (c2 == beg)
						{                                   /* short run */
							out_file->Write_Byte(128+beg-j);
							if (!out_file->Write_Byte(line_rgbe[j][i]))
								Error("Error writing HDR output data to %s.",filename);

							j = beg;
							break;
						}
				}
				while (j < beg)
				{                                       /* write out non-run */
					if ((c2 = beg-j) > 128) c2 = 128;
					out_file->Write_Byte(c2);
					while (c2--)
						if (!out_file->Write_Byte(line_rgbe[j++][i]))
							Error("Error writing HDR output data to %s.",filename);
				}
				if (cnt >= MINRUN)
				{                                 /* write out run */
					out_file->Write_Byte(128+cnt);
					if (!out_file->Write_Byte(line_rgbe[beg][i]))
						Error("Error writing HDR output data to %s.",filename);
				}
				else cnt = 0;
			}
		}

		POV_FREE(line_rgbe);
	}

	line_number++;

	out_file->flush();
}

/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

int HDR_Image::Read_Line(COLOUR *line_data)
{
	int i, j;
	int code, val;
	unsigned char *orig_image;

	if(valid == false)
		Error("Cannot access output image file.");

	if (in_file->eof())
		return 0;

	// allocate space for RGBE line
	orig_image = (unsigned char *)POV_MALLOC(4*width*sizeof(unsigned char *), "HDR image temporary data");

	// determine scanline type
	if ((width < MINELEN) | (width > MAXELEN))
	{
		i = Read_Old_Line(orig_image, width, in_file);
		RGBE_To_ColorLine(orig_image, line_data, width);
		POV_FREE(orig_image);
		return i;
	}

	if ((i = in_file->Read_Byte()) == EOF)
	{
		POV_FREE(orig_image);
		return 0;
	}

	// if everything went all right until now we can
	// increase the line number.  Anything going wrong from now on
	// will result in an error anyway.
	line_number++;

	if (i != 2)
	{
		in_file->UnRead_Byte(i);
		i = Read_Old_Line(orig_image, width, in_file);
		RGBE_To_ColorLine(orig_image, line_data, width);
		POV_FREE(orig_image);
		return i;
	}

	orig_image[1] = in_file->Read_Byte();
	orig_image[2] = in_file->Read_Byte();
	if ((i = in_file->Read_Byte()) == EOF)
	{
		POV_FREE(orig_image);
		return -1;
	}

	if ((orig_image[1] != 2) || (orig_image[2] & 128))
	{
		orig_image[0] = 2;
		orig_image[3] = i;
		i = Read_Old_Line(orig_image+4, width-1, in_file);
		RGBE_To_ColorLine(orig_image, line_data, width);
		POV_FREE(orig_image);
		return i;
	}

	if (((orig_image[2] << 8) | i) != width)
		Error("Error reading HDR data from %s.",filename);   // length mismatch!

	// read each component
	for (i = 0; i < 4; i++)
		for (j = 0; j < width; )
		{
			if ((code = in_file->Read_Byte()) == EOF)
			{
				POV_FREE(orig_image);
				return -1;
			}

			if (code > 128)
			{
				// run
				code &= 127;
				val = in_file->Read_Byte();
				while (code--)
					orig_image[i+(j++)*4] = (unsigned char)val;
			}
			else
				// non-run
				while (code--)
					orig_image[i+(j++)*4] = (unsigned char)in_file->Read_Byte();
		}

	RGBE_To_ColorLine(orig_image, line_data, width);

	// free memory
	POV_FREE(orig_image);

	return 1;
}


/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

void HDR_Image::ColorToRGBE(RGBE clr, COLC r, COLC g, COLC b)
{
  float  d;
  int  e;

  d = max3(r, g, b);

  if (d <= 1e-32) {
    clr[0] = clr[1] = clr[2] = 0;
    clr[3] = 0;
    return;
  }

  d = frexp(d, &e) * 255.9999 / d;

  clr[0] = (unsigned char) (r * d);
  clr[1] = (unsigned char) (g * d);
  clr[2] = (unsigned char) (b * d);
  clr[3] = (unsigned char) (e + 128);
}

/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/


void Read_HDR_Image(IMAGE *Image, char *filename)
{
	IStream *f;
	int height,width,cline;
	char line[BUFSIZ];
	char s1[3], s2[3];
	char *position;
	int  i, j;
	int  code, val;
	unsigned char *orig_image;
	float exposure;
	float exposure_cumul;

	if ((f = Locate_File(filename, POV_File_Image_HDR, NULL,true)) == NULL)
	{
		Error("Error opening HDR image.\n");
		return;
	}

	exposure_cumul = 1.0;
	// Header
	do
	{
		f->getline(line, BUFSIZ);
		position = strchr(line, '=');
		if (position != NULL)
		{
			*position = 0;
			if (strncmp(line,"EXPOSURE", 8)==0)
			{
#if (HDR_DEBUG == 1)
				Debug_Info("reading EXPOSURE adjustment from HDR image...\n");
#endif
				sscanf(position+1, "%f", &exposure);
				exposure_cumul *= exposure;
			}
		}
	}
	while (!f->eof() && (line[0] != '-') && (line[0] != '+'));

	// Image size
	if (sscanf(line, "%2[+-XY] %d %2[+-XY] %d\n", s1, &height, s2, &width) != 4)
		Error ("Bad HDR header");

	Image->iwidth = width;
	Image->iheight = height;
	Image->width = (DBL)Image->iwidth;
	Image->height = (DBL)Image->iheight;
	Image->Colour_Map_Size = 0;
	Image->Colour_Map = NULL;
	Image->exposure = exposure_cumul;

#if (HDR_DEBUG == 1)
	Debug_Info("HDR Read_Image: width=%d\n", width);
	Debug_Info("HDR Read_Image: height=%d\n", height);
	Debug_Info("HDR Read_Image: exposure=%d\n", exposure_cumul);
#endif

	Image->data.rgb_float_lines = (IMAGE_FLOAT_LINE *)POV_MALLOC(Image->iheight * sizeof(IMAGE_FLOAT_LINE), "HDR image");

	// allocate space for RGBE line
	orig_image = (unsigned char *)POV_MALLOC(4*width*sizeof(unsigned char *), "HDR image temporary data");

	for (cline=0;cline<height;cline++)
	{
		Image->data.rgb_float_lines[cline].red = (float *)POV_MALLOC(width*sizeof(float), "HDR image line");
		Image->data.rgb_float_lines[cline].green = (float *)POV_MALLOC(width*sizeof(float), "HDR image line");
		Image->data.rgb_float_lines[cline].blue = (float *)POV_MALLOC(width*sizeof(float), "HDR image line");

		// determine scanline type
		if ((width < MINELEN) | (width > MAXELEN))
		{
			Read_Old_Line(orig_image, width, f);
			RGBE_To_ImgLine(orig_image, &Image->data.rgb_float_lines[cline], width);
			continue;
		}

		if ((i = f->Read_Byte()) == EOF)
		{
			Warning(0, "HDR image file incomplete");

			// fill the rest of the image with black
			for (i = cline+1; i < height; i++)
			{
				Image->data.rgb_float_lines[i].red = (float *)POV_MALLOC(width*sizeof(float), "HDR image line");
				Image->data.rgb_float_lines[i].green = (float *)POV_MALLOC(width*sizeof(float), "HDR image line");
				Image->data.rgb_float_lines[i].blue = (float *)POV_MALLOC(width*sizeof(float), "HDR image line");
				for (j = 0; j < width; j++)
				{
					Image->data.rgb_float_lines[i].red[j] = 0.0;
					Image->data.rgb_float_lines[i].green[j] = 0.0;
					Image->data.rgb_float_lines[i].blue[j] = 0.0;
				}
			}

			POV_FREE(orig_image);
			delete f;
			return;
		}

		if (i != 2)
		{
			f->UnRead_Byte(i);
			Read_Old_Line(orig_image, width, f);
			RGBE_To_ImgLine(orig_image, &Image->data.rgb_float_lines[cline], width);
			continue;
		}

		orig_image[1] = f->Read_Byte();
		orig_image[2] = f->Read_Byte();
		if ((i = f->Read_Byte()) == EOF) Error("Error reading HDR file (1)");

		if ((orig_image[1] != 2) || (orig_image[2] & 128))
		{
			orig_image[0] = 2;
			orig_image[3] = i;
			Read_Old_Line(orig_image+4, width-1, f);
			RGBE_To_ImgLine(orig_image, &Image->data.rgb_float_lines[cline], width);
			continue;
		}

		if ((orig_image[2] << 8 | i) != width)
			Error("Error reading HDR file (2)");   // length mismatch!

		// read each component
		for (i = 0; i < 4; i++)
			for (j = 0; j < width; )
			{
				if ((code = f->Read_Byte()) == EOF)
					Error("Error reading HDR file (3)");
				if (code > 128)
				{
					// run
					code &= 127;
					val = f->Read_Byte();
					while (code--)
						orig_image[i+(j++)*4] = (unsigned char)val;
				}
				else
					// non-run
					while (code--)
						orig_image[i+(j++)*4] = (unsigned char) f->Read_Byte();
			}

		RGBE_To_ImgLine(orig_image, &Image->data.rgb_float_lines[cline], width);

	}

	// free memory
	POV_FREE(orig_image);

	delete f;
}
END_POV_NAMESPACE

#endif
/*-ML: hdr_patch*/
