/*******************************************************************************
 * pgm.cpp
 *
 * This module contains the code to read the PGM 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/pgm.cpp $
 * $Revision: #1 $
 * $Change: 6069 $
 * $DateTime: 2013/11/06 11:59:40 $
 * $Author: chrisc $
 *******************************************************************************/

/****************************************************************************
*
*  PGM format according to NetPBM specs (http://netpbm.sourceforge.net/doc/):
*
*  This module implements read support for PGM (grayscale) image maps.
*
*  Both ASCII and binary files are supported ('P2' and 'P5')
*  in 8 and 16 bit color depth.
*
*****************************************************************************/

#include <vector>
#include <ctype.h>

// 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/pgm.h"

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

namespace pov_base
{

namespace Pgm
{

/*****************************************************************************
*
* FUNCTION
*
*  Read_ASCII_File_Number
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*    Christoph Hormann
*
* DESCRIPTION
*
*    Reads an integer number from an ASCII file skipping whitespaces
*
* CHANGES
*
*    August 2003 - Creation
*
******************************************************************************/

int Read_ASCII_File_Number(IStream *file)
{
	int value;
	int pos = 0;
	char buffer[50] = "";

	do
	{
		value = file->Read_Byte();
	} while (isspace(value));

	if (!isdigit (value))
		throw POV_EXCEPTION(kFileDataErr, "Invalid data in PGM/PPM file");

	buffer[pos] = (char)value;

	while (pos < 48)
	{
		if ((value = file->Read_Byte()) == EOF)
			break ;
		if (isspace(value))
			break ;
		if (!isdigit(value))
			throw POV_EXCEPTION(kFileDataErr, "Invalid data in PGM/PPM file");
		buffer[++pos] = (char)value;
	}

	buffer[pos+1] = '\0';
	value = atoi(buffer);
	return value;
}

/*****************************************************************************
*
* FUNCTION
*
*  Read_PGM_Image
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*    Christoph Hormann
*
* DESCRIPTION
*
*    Reads an PGM image file
*
* CHANGES
*
*    August 2003 - New implementation based on targa/png reading code
*
******************************************************************************/

// TODO: make sure we destroy the image if we throw an exception
Image *Read (IStream *file, const Image::ReadOptions& options)
{
	int                   width;
	int                   height;
	int                   data_hi; // not unsigned as it may need to hold EOF
	int                   data_lo; // not unsigned as it may need to hold EOF
	char                  line[1024];
	char                  *ptr;
	Image                 *image = NULL;
	unsigned int          depth;
	int                   v; // not unsigned as it may need to hold EOF
	unsigned char         header[2];

	// PGM files may or may not be gamma-encoded.
	GammaCurvePtr gamma;
	if (options.gammacorrect && options.defaultGamma)
		gamma = TranscodingGammaCurve::Get(options.workingGamma, options.defaultGamma);

	// --- Read Header ---
	if (!file->read(header, 2))
		throw POV_EXCEPTION(kFileDataErr, "Cannot read header of PGM image");

	if(header[0] != 'P')
		throw POV_EXCEPTION(kFileDataErr, "File is not in PGM format");

	if ((header[1] != '2') && (header[1] != '5'))
		throw POV_EXCEPTION(kFileDataErr, "File is not in PGM format");

	// TODO FIXME - Some valid PPM files may have a different header layout regarding line breaks

	do
	{
		file->getline (line, 1024);
		line[1023] = '\0';
		if ((ptr = strchr(line, '#')) != NULL)
			*ptr = '\0';  // remove comment
	} while (line[0]=='\0');  // read until line without comment from beginning

	// --- First: two numbers: width and height ---
	if (sscanf(line,"%d %d",&width, &height) != 2)
		throw POV_EXCEPTION(kFileDataErr, "Cannot read width and height from PGM image");

	if (width <= 0 || height <= 0)
		throw POV_EXCEPTION(kFileDataErr, "Invalid width or height read from PGM image");

	do
	{
		file->getline (line, 1024) ;
		line[1023] = '\0';
		if ((ptr = strchr(line, '#')) != NULL)
			*ptr = '\0';  // remove comment
	} while (line[0]=='\0');  // read until line without comment from beginning

	// --- Second: one number: color depth ---
	if (sscanf(line,"%u",&depth) != 1)
		throw POV_EXCEPTION(kFileDataErr, "Cannot read color depth from PGM image");

	if ((depth > 65535) || (depth < 1))
		throw POV_EXCEPTION(kFileDataErr, "Unsupported number of colors in PGM image");

	if (depth < 256)
	{
		// We'll be using an image container that provides for automatic decoding if possible - unless there's no such decoding to do.
		gamma = ScaledGammaCurve::GetByDecoding(255.0f/depth, gamma); // Note that we'll apply the scaling even if we don't officially gamma-correct
		Image::ImageDataType imagetype = options.itype;
		if (imagetype == Image::Undefined)
			imagetype = ( GammaCurve::IsNeutral(gamma) ? Image::Gray_Int8 : Image::Gray_Gamma8 );
		image = Image::Create (width, height, imagetype) ;
		// NB: PGM files don't use alpha, so premultiplied vs. non-premultiplied is not an issue
		image->TryDeferDecoding(gamma, 255); // try to have gamma adjustment being deferred until image evaluation.

		for (int i = 0; i < height; i++)
		{
			if (header[1] == '2') // --- ASCII PGM file (type 2) ---
			{
				for (int x = 0; x < width; x++)
				{
					v = Read_ASCII_File_Number(file);
					SetEncodedGrayValue (image, x, i, gamma, 255, v) ;
				}
			}
			else                  // --- binary PGM file (type 5) ---
			{
				for (int x = 0; x < width; x++)
				{
					if ((v = file->Read_Byte ()) == EOF)
						throw POV_EXCEPTION(kFileDataErr, "Unexpected EOF in PGM file");
					SetEncodedGrayValue (image, x, i, gamma, 255, v) ;
				}
			}
		}
	}
	else // --- 16 bit PGM (binary or ASCII) ---
	{
		// We'll be using an image container that provides for automatic decoding if possible - unless there's no such decoding to do.
		gamma = ScaledGammaCurve::GetByDecoding(65535.0f/depth, gamma); // Note that we'll apply the scaling even if we don't officially gamma-correct
		Image::ImageDataType imagetype = options.itype;
		if (imagetype == Image::Undefined)
			imagetype = ( GammaCurve::IsNeutral(gamma) ? Image::Gray_Int16 : Image::Gray_Gamma16 );
		image = Image::Create (width, height, imagetype) ;
		// NB: PGM files don't use alpha, so premultiplied vs. non-premultiplied is not an issue
		image->TryDeferDecoding(gamma, 65535); // try to have gamma adjustment being deferred until image evaluation.

		for (int i = 0; i < height; i++)
		{
			if (header[1] == '2') // --- ASCII PGM file (type 2) ---
			{
				for (int x = 0; x < width; x++)
				{
					v = Read_ASCII_File_Number(file);
					SetEncodedGrayValue (image, x, i, gamma, 65535, v);
				}
			}
			else                  // --- binary PGM file (type 5) ---
			{
				for (int x = 0; x < width; x++)
				{
					if ((data_hi = file->Read_Byte ()) == EOF)
						throw POV_EXCEPTION(kFileDataErr, "Unexpected EOF in PGM file");
					if ((data_lo = file->Read_Byte ()) == EOF)
						throw POV_EXCEPTION(kFileDataErr, "Unexpected EOF in PGM file");
					v = (256*data_hi + data_lo);
					SetEncodedGrayValue (image, x, i, gamma, 65535, v);
				}
			}
		}
	}
	return (image) ;
}

} // end of namespace Pgm

}

