/*******************************************************************************
 * targa.cpp
 *
 * This module contains the code to read and write the Targa output file
 * format.
 *
 * ---------------------------------------------------------------------------
 * Persistence of Vision Ray Tracer ('POV-Ray') version 3.7.
 * Copyright 1991-2013 Persistence of Vision Raytracer Pty. Ltd.
 *
 * POV-Ray is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * POV-Ray 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * ---------------------------------------------------------------------------
 * POV-Ray 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.
 * ---------------------------------------------------------------------------
 * $File: //depot/public/povray/3.x/source/base/image/targa.cpp $
 * $Revision: #1 $
 * $Change: 6069 $
 * $DateTime: 2013/11/06 11:59:40 $
 * $Author: chrisc $
 *******************************************************************************/


/****************************************************************************
*
*  Explanation:
*
*  May 1994 : Support for 24-bit RLE Targa output files added: John Baily
*             and David Payne.
*
*  Jul 1994 : Resume trace support and minor algorithm fix (one more still
*             needed, see comments in Write_Targa_Line); resume will force
*             Targa format to match the original trace format -- the T or C
*             format flag is adjusted as necessary: Charles Marslett,
*
*  Jun 1995 : Added support for 32-bit Targa input and output files.
*             The alpha channel has a value of 0 for 100% transparency
*             and a value of 255 for 0% transparency. [DB]
*
*  Sep 2010 : Added metadata in Write, changing targa version from 1.0 to 2.0
*
*****************************************************************************/

#include <vector>

// configbase.h must always be the first POV file included within base *.cpp files
#include "base/configbase.h"
#include "base/image/image.h"
#include "base/image/targa.h"
#include "base/types.h"

#include "metadata.h"

// this must be the last file included
#include "base/povdebug.h"

namespace pov_base
{

namespace Targa
{

typedef struct pix
{
	unsigned int b;
	unsigned int g;
	unsigned int r;
	unsigned int a;
} Pixel;

typedef char Targa_footer[26];
#define FOO_EXT_OFF 0
#define FOO_SIG_OFF 8
#define FOO_DOT_OFF 24
#define FOO_NUL_OFF 25

typedef char Targa_extension[495];
#define EXT_SIZE_OFF 0
#define EXT_SIZE_DIM 2
#define EXT_COMMENT_OFF 43
#define EXT_COMMENT_DIM 81
#define EXT_COMMENT_COUNT 4
#define EXT_DATE_OFF 367
#define EXT_DATE_DIM 12
#define EXT_SOFT_OFF 426
#define EXT_SOFT_DIM 41
#define EXT_VERSION_OFF 467
#define EXT_VERSION_DIM 3
#define EXT_SCAN_OFF 490
#define EXT_SCAN_DIM 4
#define EXT_ALPHA_OFF 494
#define EXT_ALPHA_DIM 1
#define EXT_GAMMA_OFF 478
#define EXT_PIXRATIO_OFF 474

Pixel *GetPix (const Image *image, int x, int y, Pixel *pixel, const GammaCurvePtr& gamma, DitherHandler& dither, bool premul)
{
	GetEncodedRGBAValue (image, x, y, gamma, 255, pixel->r, pixel->g, pixel->b, pixel->a, dither, premul);
	return (pixel);
}

void PutPix (vector<unsigned char>& line, const pix *pixel, bool opaque)
{
	line.push_back (pixel->b);
	line.push_back (pixel->g);
	line.push_back (pixel->r);
	if (!opaque)
		line.push_back (pixel->a);
}

void Write (OStream *file, const Image *image, const Image::WriteOptions& options)
{
	pix                     current;
	pix                     next;
	bool                    opaque = !options.alphachannel;
	bool                    compress = options.compress > 0 ;
	vector<unsigned char>   header;
	vector<unsigned char>   line;
	GammaCurvePtr           gamma;
	Metadata                meta;
	DitherHandler*          dither = options.dither.get();

	if (options.encodingGamma)
		gamma = TranscodingGammaCurve::Get(options.workingGamma, options.encodingGamma);
	else
		gamma = TranscodingGammaCurve::Get(options.workingGamma, SRGBGammaCurve::Get());

	// TODO ALPHA - check if TGA should really keep presuming non-premultiplied alpha
	// We presume non-premultiplied alpha, unless the user overrides
	// (e.g. to handle a non-compliant file).
	bool premul = false;
	if (options.premultiplyOverride)
		premul = options.premultiply;

	header.reserve (18);
	header.push_back (0);
	header.push_back (0);

	// byte 2 - targa file type (compressed/uncompressed)
	header.push_back (options.compress ? 10 : 2);

	// Byte 3 - Index of first color map entry LSB
	// Byte 4 - Index of first color map entry MSB
	// Byte 5 - Color map length LSB
	// Byte 6 - Color map legth MSB
	// Byte 7 - Color map size
	// x origin set to "First_Column" - Bytes 8, 9
	// y origin set to "First_Line"   - Bytes 10, 11
	for (int i = 0; i < 9; i++)
		header.push_back (0);

	// write width and height - Bytes 12 - 15
	unsigned int w = image->GetWidth();
	unsigned int h = image->GetHeight();
	header.push_back (w % 256);
	header.push_back (w / 256);
	header.push_back (h % 256);
	header.push_back (h / 256);

	if (!opaque)
	{
		header.push_back(32);    /* 32 bits/pixel (BGRA) */
		header.push_back(0x28);  /* Data starts at top left, 8 bits Alpha */
	}
	else
	{
		header.push_back(24);    /* 24 bits/pixel (BGR) */
		header.push_back(0x20);  /* Data starts at top left, 0 bits Alpha */
	}

	if (!file->write (&header[0], 18))
		throw POV_EXCEPTION(kFileDataErr, "header write failed for targa file") ;

	line.reserve (w * 4);
	if (options.compress == 0)
	{
		Pixel pixel;
		for (int row = 0; row  < h; row++)
		{
			line.clear ();
			for (int col = 0; col < w; col++)
				PutPix (line, GetPix (image, col, row, &pixel, gamma, *dither, premul), opaque);
			if (!file->write (&line[0], line.size()))
				throw POV_EXCEPTION(kFileDataErr, "row write failed for targa file") ;
		}
	}
	else
	{
		// RLE compressed data

		for (int row = 0; row  < h; row++)
		{
			line.clear ();

			int llen = w;
			int startx = 0;
			int cnt = 1;
			int ptype = 0;
			bool writenow = false;

			GetPix (image, 0, row, &current, gamma, *dither, premul);
			while (true)
			{
				if (startx + cnt < llen)
					GetPix (image, startx + cnt, row, &next, gamma, *dither, premul);
				else
					next = current;
				if (memcmp (&current, &next, sizeof (pix)) == 0)
				{
					if (ptype == 0)
					{
						cnt++;
						if ((cnt >= 128) || ((startx + cnt) >= llen))
							writenow = true;
					}
					else
					{
						cnt--;
						writenow = true;
					}
				}
				else
				{
					if ((ptype == 1) || (cnt <= 1))
					{
						current = next;
						ptype = 1;
						cnt++;
						if ((cnt >= 128) || ((startx + cnt) >= llen))
							writenow = true;
					}
					else
						writenow = true;
				}

				if (writenow)
				{
					Pixel pixel;
					/* This test SHOULD be unnecessary!  However, it isn't!  [CWM] */
					if (startx + cnt > llen)
						cnt = llen - startx;
					if (ptype == 0)
					{
						line.push_back ((unsigned char) ((cnt - 1) | 0x80));
						PutPix (line, &current, opaque);
						current = next;
					}
					else
					{
						line.push_back ((unsigned char) cnt - 1);
						for (int x = 0; x < cnt; x++)
							PutPix (line, GetPix (image, startx + x, row, &pixel, gamma, *dither, premul), opaque);
					}
					startx += cnt;
					writenow = false;
					ptype = 0;
					cnt = 1;
					if (!file->write (&line[0], line.size()))
						throw POV_EXCEPTION(kFileDataErr, "write failed for targa file") ;
					if (startx >= llen)
						break;
					line.clear ();
				}
			}
		}
	}

	POV_LONG curpos;
	Targa_extension ext;
	Targa_footer foo;

	// A lot of "unused" get happy when filled with 0
	memset(ext,0,sizeof(ext));
	memset(foo,0,sizeof(foo));
	curpos = file->tellg();

	foo[FOO_EXT_OFF+3]= 0x0ff & (curpos >> 24);
	foo[FOO_EXT_OFF+2]= 0x0ff & (curpos >> 16);
	foo[FOO_EXT_OFF+1]= 0x0ff & (curpos >> 8);
	foo[FOO_EXT_OFF]= 0x0ff & (curpos);

	// fill signature, Reserved char & binary string terminator in one go
	sprintf(&foo[FOO_SIG_OFF],"TRUEVISION-XFILE.");

	// let's prepare the Extension area, then write Extension then footer
	ext[EXT_SIZE_OFF+1] = (unsigned char) ((495 >> 8) & 0xff);
	ext[EXT_SIZE_OFF] = (unsigned char) (495 & 0xff);
	string soft = meta.getSoftware().substr(0,EXT_SOFT_DIM-1);
	sprintf(&ext[EXT_SOFT_OFF],"%s",soft.c_str());

	string com = meta.getComment1().substr(0,EXT_COMMENT_DIM-1);
	if (!com.empty())
		sprintf(&ext[EXT_COMMENT_OFF],"%s",com.c_str());
	com = meta.getComment2().substr(0,EXT_COMMENT_DIM-1);
	if (!com.empty())
		sprintf(&ext[EXT_COMMENT_OFF+EXT_COMMENT_DIM],"%s",com.c_str());
	com = meta.getComment3().substr(0,EXT_COMMENT_DIM-1);
	if (!com.empty())
		sprintf(&ext[EXT_COMMENT_OFF+EXT_COMMENT_DIM*2],"%s",com.c_str());
	com = meta.getComment4().substr(0,EXT_COMMENT_DIM-1);
	if (!com.empty())
		sprintf(&ext[EXT_COMMENT_OFF+EXT_COMMENT_DIM*3],"%s",com.c_str());

	ext[EXT_DATE_OFF]    = meta.getMonth();
	ext[EXT_DATE_OFF+1]  = 0;
	ext[EXT_DATE_OFF+2]  = meta.getDay();
	ext[EXT_DATE_OFF+3]  = 0;
	ext[EXT_DATE_OFF+4]  = meta.getYear()%256;
	ext[EXT_DATE_OFF+5]  = meta.getYear()/256;
	ext[EXT_DATE_OFF+6]  = meta.getHour();
	ext[EXT_DATE_OFF+7]  = 0;
	ext[EXT_DATE_OFF+8]  = meta.getMin();
	ext[EXT_DATE_OFF+9]  = 0;
	ext[EXT_DATE_OFF+10] = meta.getSec();
	ext[EXT_DATE_OFF+11] = 0;

	// we do not use the version field, but All 0 is not good enough
	ext[EXT_VERSION_OFF]=0;
	ext[EXT_VERSION_OFF+1]=0;
	ext[EXT_VERSION_OFF+2]=' ';

	// scanline offset is 18 (as 4 bytes) as we did not fill image ID nor color map 
	ext[EXT_SCAN_OFF] = 18;

	// alpha handling
	ext[EXT_ALPHA_OFF] = opaque ? 0:premul ?4:3; 

	// gamma handling (range 0.0 to 10.0, on short, hard code a denominator to allow 10.0)
	ext[EXT_GAMMA_OFF] = (int)(6553.0 * gamma->ApproximateDecodingGamma()) % 256;
	ext[EXT_GAMMA_OFF+1] = (int)(6553.0 * gamma->ApproximateDecodingGamma()) / 256;
	ext[EXT_GAMMA_OFF+2] = (unsigned char) (6553 % 256);
	ext[EXT_GAMMA_OFF+3] = (unsigned char) (6553 / 256) ;
	file->write(ext,sizeof(ext));
	file->write(foo,sizeof(foo));
}

void ConvertColor (Pixel *pixel, unsigned pixelsize, const unsigned char *bytes)
{
	unsigned char r;
	unsigned char g;
	unsigned char b;
	unsigned char a = 255; // default to solid colour

	switch (pixelsize)
	{
		case 8:
			r = g = b = bytes[0];
			break;

		case 15:
			r = ((bytes[1] & 0x7c) << 1);
			g = (((bytes[1] & 0x03) << 3) | ((bytes[0] & 0xe0) >> 5)) << 3;
			b = (bytes[0] & 0x1f) << 3;
			break;

		case 16:
			r = ((bytes[1] & 0x7c) << 1);
			g = (((bytes[1] & 0x03) << 3) | ((bytes[0] & 0xe0) >> 5)) << 3;
			b = (bytes[0] & 0x1f) << 3;
			a = bytes[1] & 0x80 ? 255 : 0;
			break;

		case 24:
			r = bytes[2];
			g = bytes[1];
			b = bytes[0];
			break;

		case 32:
			r = bytes[2];
			g = bytes[1];
			b = bytes[0];
			a = bytes[3];
			break;

		default:
			throw POV_EXCEPTION(kParamErr, "Bad pixelsize in targa color");
	}

	pixel->r = r;
	pixel->g = g;
	pixel->b = b;
	pixel->a = a;
}

void ConvertColor (Image::RGBAMapEntry *pixel, unsigned pixelsize, const unsigned char *bytes, const GammaCurvePtr& gamma)
{
	unsigned char r;
	unsigned char g;
	unsigned char b;
	unsigned char a = 255; // default to solid colour

	switch (pixelsize)
	{
		case 8:
			r = g = b = bytes[0];
			break;

		case 15:
			r = ((bytes[1] & 0x7c) << 1);
			g = (((bytes[1] & 0x03) << 3) | ((bytes[0] & 0xe0) >> 5)) << 3;
			b = (bytes[0] & 0x1f) << 3;
			break;

		case 16:
			r = ((bytes[1] & 0x7c) << 1);
			g = (((bytes[1] & 0x03) << 3) | ((bytes[0] & 0xe0) >> 5)) << 3;
			b = (bytes[0] & 0x1f) << 3;
			a = bytes[1] & 0x80 ? 255 : 0;
			break;

		case 24:
			r = bytes[2];
			g = bytes[1];
			b = bytes[0];
			break;

		case 32:
			r = bytes[2];
			g = bytes[1];
			b = bytes[0];
			a = bytes[3];
			break;

		default:
			throw POV_EXCEPTION(kParamErr, "Bad pixelsize in targa color");
	}

	pixel->red   = IntDecode(gamma, r, 255);
	pixel->green = IntDecode(gamma, g, 255);
	pixel->blue  = IntDecode(gamma, b, 255);
	pixel->alpha = IntDecode(a, 255);
}

/*****************************************************************************
*
* FUNCTION
*
*   Read_Targa_Image
*
* INPUT
*   
* OUTPUT
*   
* RETURNS
*   
* AUTHOR
*
*   POV-Ray Team
*   
* DESCRIPTION
*
*   Reads a Targa image into an RGB image buffer.  Handles 8, 16, 24, 32 bit
*   formats.  Raw or color mapped. Simple raster and RLE compressed pixel
*   encoding. Right side up or upside down orientations.
*
* CHANGES
*
*   Jun 1995 : Added code for 32 bit Targa files. [DB]
*
******************************************************************************/

Image *Read (IStream *file, const Image::ReadOptions& options)
{
	int                   temp;
	int                   h;
	unsigned int          ftype;
	unsigned int          idlen;
	unsigned int          cmlen;
	unsigned int          cmsiz;
	unsigned int          cmsizB;
	unsigned int          psize;
	unsigned int          psizeB;
	unsigned int          orien;
	unsigned int          width;
	unsigned int          height;
	unsigned char         bytes[256];
	unsigned char         tgaheader[18];
	Pixel                 pixel;
	Image                 *image ;

	// TARGA files have no clearly defined gamma by default.
	// Since 1989, TARGA 2.0 defines gamma metadata which could be used if present.
	// However, as of now (2009), such information seems to be rarely included, if at all.
	// We're defaulting to sRGB because that's what we write, and the most common gamma appears to be 2.2
	GammaCurvePtr gamma;
	if (options.gammacorrect)
	{
		if (options.defaultGamma)
			gamma = TranscodingGammaCurve::Get(options.workingGamma, options.defaultGamma);
		else
			gamma = TranscodingGammaCurve::Get(options.workingGamma, SRGBGammaCurve::Get());
	}

	// TODO ALPHA - check if TGA should really keep presuming non-premultiplied alpha
	// We presume non-premultiplied alpha, so that's the preferred mode to use for the image container unless the user overrides
	// (e.g. to handle a non-compliant file).
	bool premul = false;
	if (options.premultiplyOverride)
		premul = options.premultiply;

	if (!file->read (tgaheader, 18))
		throw POV_EXCEPTION(kFileDataErr, "Cannot read targa file header");

	/* Decipher the header information */
	idlen  = tgaheader[ 0];
	ftype  = tgaheader[ 2];
	cmlen  = tgaheader[ 5] + (tgaheader[ 6] << 8);
	cmsiz  = tgaheader[ 7];
	width  = tgaheader[12] + (tgaheader[13] << 8);
	height = tgaheader[14] + (tgaheader[15] << 8);
	psize  = tgaheader[16];
	orien  = tgaheader[17] & 0x20; /* Right side up ? */

	cmsizB = (cmsiz+7)/8;
	psizeB = (psize+7)/8;

	bool opaque = (psize <= 24) && (psize != 16);
	bool compressed = ftype == 9 || ftype == 10 || ftype == 11;

	/* Determine if this is a supported Targa type */
	if (ftype < 1 || ftype > 11 || (ftype > 3 && ftype < 9))
		throw POV_EXCEPTION(kFileDataErr, "Unsupported file type in targa image");
	if (psize != 8 && psize != 15 && psize != 16 && psize != 24 && psize != 32)
		throw POV_EXCEPTION(kFileDataErr, "Unsupported pixel size in targa image");
	if (cmlen > 256)
		throw POV_EXCEPTION(kFileDataErr, "Unsupported color map length in targa image");

	/* Skip over the picture ID information */
	if (idlen > 0)
		if (!file->read (bytes, idlen))
			throw POV_EXCEPTION(kFileDataErr, "Cannot read from targa image");

	/* Read in the the color map (if any) */
	if (cmlen > 0)
	{
		if (psize != 8)
			throw POV_EXCEPTION(kFileDataErr, "Unsupported color map bit depth in targa image");

		vector<Image::RGBAMapEntry> colormap ;
		Image::RGBAMapEntry entry;
		for (int i = 0; i < cmlen; i++)
		{
			for (int j = 0; j < cmsizB; j++)
			{
				if ((temp = file->Read_Byte ()) == EOF)
					throw POV_EXCEPTION(kFileDataErr, "Cannot read color map from targa image");
				bytes[j] = (unsigned char) temp;
			}
			ConvertColor (&entry, cmsiz, bytes, gamma);
			colormap.push_back(entry);
		}
		Image::ImageDataType imagetype = options.itype;
		if (imagetype == Image::Undefined)
			imagetype = Image::Colour_Map ;
		image = Image::Create (width, height, imagetype, colormap) ;
		image->SetPremultiplied(premul); // specify whether the color map data has premultiplied alpha
		gamma.reset(); // gamma has been taken care of by transforming the color table.
	}
	else
	{
		Image::ImageDataType imagetype = options.itype;
		if (imagetype == Image::Undefined)
		{
			if (GammaCurve::IsNeutral(gamma))
				// No gamma correction required, raw values can be stored "as is".
				imagetype = opaque ? Image::RGB_Int8 : Image::RGBA_Int8 ;
			else
				// Gamma correction required; use an image container that will take care of that.
				imagetype = opaque ? Image::RGB_Gamma8 : Image::RGBA_Gamma8 ;
		}
		image = Image::Create (width, height, imagetype) ;
		image->SetPremultiplied(premul); // set desired storage mode regarding alpha premultiplication
		image->TryDeferDecoding(gamma, 255); // try to have gamma adjustment being deferred until image evaluation.
	}

	if (compressed)
	{
		/* RLE compressed images */
		int x = 0;
		int y = 0;
		while (y < height)
		{
			int line = orien != 0 ? y : height - y - 1 ;

			/* Grab a header */
			if ((h = file->Read_Byte ()) == EOF)
				throw POV_EXCEPTION(kFileDataErr, "Cannot read data from targa image");
			if (h & 0x80)
			{
				h &= 0x7F;
				if (cmlen == 0)
				{
					for (int k = 0; k < psizeB; k++)
					{
						if ((temp = file->Read_Byte ()) == EOF)
							throw POV_EXCEPTION(kFileDataErr, "Cannot read data from targa image");
						bytes[k] = (unsigned char) temp;
					}
					ConvertColor (&pixel, psize, bytes);
					while (h-- >= 0)
					{
						SetEncodedRGBAValue (image, x, line, gamma, 255, pixel.r, pixel.g, pixel.b, pixel.a, premul);
						if (++x == width)
						{
							y++;
							x = 0;
						}
					}
				}
				else
				{
					if ((temp = file->Read_Byte ()) == EOF)
						throw POV_EXCEPTION(kFileDataErr, "Cannot read data from targa image");
					if ((unsigned char) temp >= cmlen)
						throw POV_EXCEPTION(kFileDataErr, "Invalid color map index in targa image");
					while (h-- >= 0)
					{
						image->SetIndexedValue (x, line, (unsigned char) temp) ;
						if (++x == width)
						{
							y++;
							x = 0;
						}
					}
				}

			}
			else
			{
				/* Copy buffer */
				while (h-- >= 0)
				{
					if (cmlen == 0)
					{
						for (int k = 0; k < psizeB; k++)
						{
							if ((temp = file->Read_Byte ()) == EOF)
								throw POV_EXCEPTION(kFileDataErr, "Cannot read data from targa image");
							bytes[k] = (unsigned char) temp;
						}
						ConvertColor (&pixel, psize, bytes);
						SetEncodedRGBAValue (image, x, line, gamma, 255, pixel.r, pixel.g, pixel.b, pixel.a, premul);
					}
					else
					{
						if ((temp = file->Read_Byte ()) == EOF)
							throw POV_EXCEPTION(kFileDataErr, "Cannot read data from targa image");
						if ((unsigned char) temp >= cmlen)
							throw POV_EXCEPTION(kFileDataErr, "Invalid color map index in targa image");
						image->SetIndexedValue (x, line, (unsigned char) temp) ;
					}

					if (++x == width)
					{
						if (++y == height)
							break ;
						line = orien != 0 ? y : height - y - 1 ;
						x = 0;
					}
				}
			}
		}
	}
	else
	{
		/* Simple raster image file, read in all of the pixels */
		for (int y = 0; y < height; y++)
		{
			int line = orien != 0 ? y : height - y - 1 ;
			for (int x = 0; x < width; x++)
			{
				if (cmlen == 0)
				{
					for (int z = 0; z < psizeB; z++)
					{
						if ((temp = file->Read_Byte ()) == EOF)
							throw POV_EXCEPTION(kFileDataErr, "Cannot read data from targa image");
						bytes[z] = (unsigned char) temp;
					}
					ConvertColor (&pixel, psize, bytes);
					SetEncodedRGBAValue (image, x, line, gamma, 255, pixel.r, pixel.g, pixel.b, pixel.a, premul);
				}
				else
				{
					if ((temp = file->Read_Byte ()) == EOF)
						throw POV_EXCEPTION(kFileDataErr, "Cannot read data from targa image");
					if ((unsigned char) temp >= cmlen)
						throw POV_EXCEPTION(kFileDataErr, "Invalid color map index in targa image");
					image->SetIndexedValue (x, line, (unsigned char) temp) ;
				}
			}
		}
	}

	return (image);
}

} // end of namespace Targa

}

