/*
 * Copyright (C) 1997-2009, R3vis Corporation.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA, or visit http://www.gnu.org/copyleft/lgpl.html.
 *
 * Original Contributor:
 *   Wes Bethel, R3vis Corporation, Marin County, California
 *   http://www.r3vis.com/
 * Additional Contributor(s):
 *
 * The OpenRM project is located at http://openrm.sourceforge.net/.
 */
/*
 * $Id: rmppm.c,v 1.7 2005/02/19 16:10:45 wes Exp $
 * Version: $Name: v180-alpha-02 $
 * $Revision: 1.7 $
 * $Log: rmppm.c,v $
 * Revision 1.7  2005/02/19 16:10:45  wes
 * Distro sync and consolidation.
 *
 * Revision 1.6  2005/01/23 17:09:39  wes
 * Copyright update to 2005.
 *
 * Revision 1.5  2004/03/18 15:51:16  wes
 * Minor changes to switch statements to avoid compile warning on SGI
 * about "statement not reached" when there is a return inside a case.
 *
 * Revision 1.4  2004/01/17 04:09:05  wes
 * Updated copyright line for 2004.
 *
 * Revision 1.3  2003/06/20 01:38:23  wes
 * Minor documentation tweaks.
 *
 * Revision 1.2  2003/02/02 02:07:21  wes
 * Updated copyright to 2003.
 *
 * Revision 1.1.1.1  2003/01/28 02:15:23  wes
 * Manual rebuild of rm150 repository.
 *
 * Revision 1.3  2003/01/16 22:21:19  wes
 * Updated all source files to reflect new organization of header files:
 * all header files formerly located in include/rmaux, include/rmi, include/rmv
 * are now located in include/rm.
 *
 * Revision 1.2  2002/06/02 15:18:45  wes
 * Fixed minor indexing bug.
 *
 * Revision 1.1  2001/06/03 20:18:15  wes
 * Initial entry (v140-beta1)
 *
 */

/*
 * file: rmppm.c
 *
 * PGM/PPM input/output
 *
 * OpenRM 1.4.0-alpha2
 *
 * 02/23/01 jdb
 */

#include <rm/rm.h>
#include <rm/rmi.h>

/*#define DEBUG*/

/*
 * ----------------------------------------------------
 * @Name rmiReadPPM
 @pstart
 RMimage * rmiReadPPM (const char *  filename,
	               RMenum        format,
		       unsigned char alpha)
 @pend

 @astart
 char * filename - filename of PGM/PPM image file to read
 RMenum format - image format: RM_IMAGE_ALPHA, RM_IMAGE_LUMINANCE,
     RM_IMAGE_RGB, RM_IMAGE_RGBA
 unsigned char alpha - alpha for RMimage [0..255]
 @aend

 @dstart

 Read in a PGM/PPM file and return a handle to the RMimage object
 created from the image file.

 Certain simplifying assumptions are made about the file format:

 <pre>
    image header
       - the magic number <CR>
       - comments (lines starting with "#") may follow <CR>
       - the width and height follow, delineated by whitespace <CR>
       - comments may follow <CR>
       - maximum value parameter follows, delineated by whitespace <CR>
       
       P3
       # this is an example 128x128 PPM file (8-bit RGB pixels)
       128 128
       255
       [image data]

    image pixels
       - ASCII lines may be no more than 70 characters (PBM/PGM/PPM spec)
       - image data is assumed to be ordered top to bottom, left to right
 </pre>
 The following RMimage formats are supported:
    RM_IMAGE_ALPHA, RM_IMAGE_LUMINANCE, RM_IMAGE_RGB, RM_IMAGE_RGBA

 The user-specified image format controls how the image is read in as
 an RMimage:

 <pre>
    PGM files: map directly to RM_IMAGE_ALPHA + RM_IMAGE_LUMINANCE
               channel replicated RGB to RM_IMAGE_RGB 
               channel replicated RGB to RM_IMAGE_RGBA with alpha

    PPM files: image luminance to RM_IMAGE_ALPHA + RM_IMAGE_LUMINANCE
               map directly to RM_IMAGE_RGB
	       map directly to RM_IMAGE_RGBA with alpha
 </pre>

 Currently, pixels must have type RM_UNSIGNED_BYTE.

 @dend
 * ----------------------------------------------------
 */
RMimage *
rmiReadPPM (const char *  filename,
	    RMenum        format,
	    unsigned char alpha)
{
    int             i, type, width, height, max;
    char            buffer[80];
    FILE *          fp;
    unsigned char * pixels = NULL;
    RMimage *       image = NULL;

    /* verify legal format for rmiReadPPM */
    if ((format == RM_IMAGE_ALPHA) || (format == RM_IMAGE_LUMINANCE)
	|| (format == RM_IMAGE_RGB) || (format == RM_IMAGE_RGBA))
    {
	
	/* open input file */
	if ((fp = fopen (filename, "r")) != NULL)
	{
	    /* get magic number */
	    fgets (buffer, 70, fp);
	    sscanf (buffer, "P%d", &type);
	    
	    /* get width & height */
	    do
	    {
		fgets (buffer, 70, fp);
	    }
	    while (buffer[0] == '#');
	    sscanf (buffer, "%d %d", &width, &height);
	    
	    /* get component maximum value (RM_UNSIGNED_BYTE) */
	    do
	    {
		fgets (buffer, 70, fp);
	    }
	    while (buffer[0] == '#');
	    sscanf (buffer, "%d", &max);
	    
#ifdef DEBUG
	    printf ("reading image file: %s\n", filename);
	    printf ("type = %d\n", type);
	    printf ("width = %d\nheight = %d\n", width, height);
	    printf ("max = %d\n", max);
#endif

	    /* read in pixels */
	    switch (type)
	    {
		case RMI_PPM_P2: /* ASCII greyscale image */
		{
		    switch (format)
		    {
			case RM_IMAGE_ALPHA: /* 1 component per pixel */
			case RM_IMAGE_LUMINANCE:
			{
			    unsigned int cmp;
			    int          count = width * height;
			    
			    pixels = malloc (count * sizeof (unsigned char));
			    for (i = 0; i < count; i++)
			    {
				fscanf (fp, "%d", &cmp);
				*(pixels + i) = (unsigned char)cmp;
			    }
			}
			break;
			
			case RM_IMAGE_RGB: /* 3 components per pixel */
			{
			    unsigned int cmp;
			    int          count = width * height * 3;
			    
			    pixels = malloc (count * sizeof (unsigned char));
			    for (i = 0; i < count; i += 3)
			    {
				fscanf (fp, "%d", &cmp);
				*(pixels + i) = *(pixels + i + 1) = *(pixels + i + 2) = (unsigned char)cmp;
			    }
			}
			break;
			
			case RM_IMAGE_RGBA: /* 4 components per pixel */
			{
			    unsigned int cmp;
			    int          count = width * height * 4;
			    
			    pixels = malloc (count * sizeof (unsigned char));
			    for (i = 0; i < count; i += 4)
			    {
				fscanf (fp, "%d", &cmp);
				*(pixels + i) = *(pixels + i + 1) = *(pixels + i + 2) = (unsigned char)cmp;
				*(pixels + i + 3) = alpha;
			    }
			}
			break;
			
			default: /* bogus image format */
			break;
		    }
		}
		break;
		
		case RMI_PPM_P3: /* ASCII RGB image */
		{
		    switch (format)
		    {
			case RM_IMAGE_ALPHA: /* 1 component per pixel */
			case RM_IMAGE_LUMINANCE:
			{
			    int          count = width * height;
			    unsigned int color[3];

			    pixels = malloc (count * sizeof (unsigned char));

			    /* compute "standard" luminance image (RGB --> greyscale) */
			    for (i = 0; i < count; i++)
			    {
				fscanf (fp, "%d %d %d", color, (color + 1), (color + 2));
				*(pixels + i) = (unsigned char)((0.28 * color[0]) + (0.59 * color[1]) + (0.13 * color[2]));
			    }
			}
			break;

			case RM_IMAGE_RGB: /* 3 components per pixel */
			{
			    unsigned int cmp;
			    int          count = width * height * 3;    
			    
			    pixels = malloc (count * sizeof (unsigned char));
			    for (i = 0; i < count; i += 3)
			    {
				fscanf (fp, "%d", &cmp);
				*(pixels + i) = (unsigned char)cmp;
				fscanf (fp, "%d", &cmp);
				*(pixels + i + 1) = (unsigned char)cmp;
				fscanf (fp, "%d", &cmp);
				*(pixels + i + 2) = (unsigned char)cmp;
			    }
			}
			break;
			
			case RM_IMAGE_RGBA: /* 4 components per pixel */
			{
			    unsigned int cmp;
			    int          count = width * height * 4;
			    
			    pixels = malloc (count * sizeof (unsigned char));
			    for (i = 0; i < count; i += 4)
			    {
				fscanf (fp, "%d", &cmp);
				*(pixels + i) = (unsigned char)cmp;
				fscanf (fp, "%d", &cmp);
				*(pixels + i + 1) = (unsigned char)cmp;
				fscanf (fp, "%d", &cmp);
				*(pixels + i + 2) = (unsigned char)cmp;
				*(pixels + i + 3) = alpha;
			    }
			}
			break;
			
			default: /* bogus image format */
			break;
		    }
		}
		break;
		
		case RMI_PPM_P5: /* binary greyscale image */
		{
		    switch (format)
		    {
			case RM_IMAGE_ALPHA: /* 1 component per pixel */
			case RM_IMAGE_LUMINANCE:
			{
			    int count = width * height;
			    
			    pixels = malloc (count * sizeof (unsigned char));
			    fread (pixels, 1, count, fp);
			}
			break;
			
			case RM_IMAGE_RGB: /* 3 components per pixel */
			{
			    int count = width * height * 3;
			    
			    pixels = malloc (count * sizeof (unsigned char));
			    for (i = 0; i < count; i += 3)
			    {
				fread ((pixels + i), 1, 1, fp);
				*(pixels + i + 1) = *(pixels + i + 2) = *(pixels + i);
			    }
			    
			}
			break;
			
			case RM_IMAGE_RGBA: /* 4 components per pixel */
			{
			    int count = width * height * 4;
			    
			    pixels = malloc (count * sizeof (unsigned char));
			    for (i = 0; i < count; i += 4)
			    {
				fread ((pixels + i), 1, 1, fp);
				*(pixels + i + 1) = *(pixels + i + 2) = *(pixels + i);
				*(pixels + i + 3) = alpha;
			    }
			}
			break;

			default: /* bogus image format */
			break;
		    }
		}
		break;
		
		case RMI_PPM_P6: /* binary RGB image */
		{
		    switch (format)
		    {
			case RM_IMAGE_ALPHA: /* 1 component per pixel */
			case RM_IMAGE_LUMINANCE:
			{
			    int           count = width * height;
			    unsigned char color[3];

			    pixels = malloc (count * sizeof (unsigned char));

			    /* compute "standard" luminance image (RGB --> greyscale) */
			    for (i = 0; i < count; i++)
			    {
				fread (color, 1, 3, fp);
				*(pixels + i) = (unsigned char)((0.28 * color[0]) + (0.59 * color[1]) + (0.13 * color[2]));
			    }
			}
			break;
			
			case RM_IMAGE_RGB: /* 3 components per pixel */
			{
			    int count = width * height * 3;
			    
			    pixels = malloc (count * sizeof (unsigned char));
			    fread (pixels, 1, count, fp);
			}
			break;
			
			case RM_IMAGE_RGBA: /* 4 components per pixel */
			{
			    int count = width * height * 4;
			    
			    pixels = malloc (count * sizeof (unsigned char));
			    for (i = 0; i < count; i += 4)
			    {
				fread ((pixels + i), 1, 3, fp);
				*(pixels + 3) = alpha;
			    }
			}
			break;
			
			default: /* bogus image format */
			break;
		    }
		}
		break;
		
		default:
		{
		    sprintf (buffer, "rmiReadPPM () : bogus magic number in image header: %s\n", filename);
		    rmError (buffer);
		    fclose (fp);
		    return (NULL);
		}
	    }
	    
	    /* stuff pixels into image object, discard pixel store, close input file */
	    image = rmImageNew (2, width, height, 1, format, RM_UNSIGNED_BYTE, RM_COPY_DATA);  
	    rmImageSetPixelData (image, (void *)pixels, RM_COPY_DATA, NULL);
	    free (pixels);
	    fclose (fp);
	}
	else
	{
	    sprintf (buffer, "rmiReadPPM () : cannot open file \"%s\" for input\n", filename);
	    rmError (buffer);
	    return (NULL);
	}
    }
    else
    {
	rmError ("rmiReadPPM () : invalid RMimage format\n");
	return (NULL);
    }
    return (image);
}
 

/*
 * ----------------------------------------------------
 * @Name rmiWritePPM
 @pstart
 RMenum rmiWritePPM (const char *    filename,
	             int             type,
	             const RMimage * image)
 @pend

 @astart
 const char * filename - filename for RMimage write
 int type - image file type (RMI_PPM_ASCII or RMI_PPM_BINARY)
 const RMimage * image - image to write
 @aend

 @dstart

 Write the user-supplied RMimage to a PGM/PPM image file.  The type of
 file written is inferred from the RMimage format and user-supplied
 type. Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 The following RMimage formats are supported by PBM/PGM/PPM files:
 <pre>
    RM_IMAGE_ALPHA, RM_IMAGE_LUMINANCE (output as PGM) 
    RM_IMAGE_RGB (outputs as PPM)
    RM_IMAGE_RGBA (outputs as PPM, alpha discarded)
 </pre>

 The file format follows the PGM/PPM spec, as described in the
 docs for rmiWritePPM ().

 Currently, pixel components must have type RM_UNSIGNED_BYTE.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmiWritePPM (const char *    filename,
	     int             type,
	     const RMimage * image)
{
    int             i;
    char            buffer[80];
    FILE *          fp;
    unsigned char * pixels;

    /* open output file */
    if ((fp = fopen (filename, "w")) != NULL)
    {
	int dim, width, height, depth, comp;
	
	/* verify legal format for rmiWritePPM */
	if ((rmImageGetType (image) == RM_UNSIGNED_BYTE) && 
	    (rmImageGetImageSize (image, &dim, &width, &height, &depth, &comp, NULL) == RM_CHILL) &&
	    (pixels = (unsigned char *)(rmImageGetPixelData (image))) != NULL)
	{

#ifdef DEBUG
	    printf ("writing image file: %s\n", filename);
	    printf ("dim = %d\n", dim);
	    printf ("width = %d\nheight = %d\n", width, height);
	    printf ("comp = %d\n", comp);
#endif

	    switch (rmImageGetFormat (image))
	    {
		/* PGM (1 component per pixel) */
		case RM_IMAGE_ALPHA:      
		case RM_IMAGE_LUMINANCE:
		{
		    /* write header */
		    switch (type)
		    {
			case RMI_PPM_ASCII:
			{
			    /* write header */
			    fprintf (fp, "P2\n");
			    fprintf (fp, "%d %d\n", width, height);
			    fprintf (fp, "%d\n", 255); /* current component type is RM_UNSIGNED_BYTE */

			    /* write pixels */
			    for (i = 0; i < (width * height); i++)
			    {
				if ((!(i % 15)) && (i >= 15)) /* 5 image components per line */
				    fprintf (fp, "\n");
				
				fprintf (fp, "%3d ", *(pixels + i));
			    }
			}
			break;
			
			case RMI_PPM_BINARY:
			{
			    /* write header */
			    fprintf (fp, "P5\n");
			    fprintf (fp, "%d %d\n", width, height);
			    fprintf (fp, "%d\n", 255); /* current component type is RM_UNSIGNED_BYTE */

			    /* write pixels */
			    fwrite(pixels, 1, (width * height), fp);
			}
			break;
			    
			default:
			{
			    rmError ("rmiWritePPM () : bogus type for writing.\n");
			}
			break;
		    }
		}
		break;
		
		/* PPM (3 components per pixel) */
		case RM_IMAGE_RGB:
		{
		    switch (type)
		    {
			case RMI_PPM_ASCII:
			{
			    /* write header */
			    fprintf (fp, "P3\n");
			    fprintf (fp, "%d %d\n", width, height);
			    fprintf (fp, "%d\n", 255); /* current component type is RM_UNSIGNED_BYTE */

			    /* write pixels */
			    for (i = 0; i < (width * height * 3); i += 3)
			    {
				if ((!(i % 15)) && (i >= 15)) /* 5 image components per line */
				    fprintf (fp, "\n");
				
				fprintf (fp, "%3d %3d %3d ", *(pixels + i), *(pixels + i + 1), *(pixels + i + 2));
			    }
			}
			break;
			
			case RMI_PPM_BINARY:
			{
			    /* write header */
			    fprintf (fp, "P6\n");
			    fprintf (fp, "%d %d\n", width, height);
			    fprintf (fp, "%d\n", 255); /* current component type is RM_UNSIGNED_BYTE */
			    
			    /* write pixels */
			    fwrite (pixels, 1, (width * height * 3), fp);
			}
			break;
			
			default:
			{
			    rmError ("rmiWritePPM () : bogus type for writing.\n");
			}
			break;
		    }
		}
		break;
		
		/* PPM (3 components per pixel, alpha is discarded) */
		case RM_IMAGE_RGBA:
		{
		    switch (type)
		    {
			case RMI_PPM_ASCII:
			{
			    /* write header */
			    fprintf (fp, "P3\n");
			    fprintf (fp, "%d %d\n", width, height);
			    fprintf (fp, "%d\n", 255); /* current component type is RM_UNSIGNED_BYTE */

			    /* write pixels */
			    for (i = 0; i < (width * height * 4); i += 4)
			    {
				if ((!(i % 15)) && (i >= 15)) /* 5 image components per line */
				    fprintf (fp, "\n");
				
				fprintf (fp, "%3d %3d %3d ", *(pixels + i), *(pixels + i + 1), *(pixels + i + 2));
			    }
			}
			break;
			
			case RMI_PPM_BINARY:
			{
			    /* write header */
			    fprintf (fp, "P6\n");
			    fprintf (fp, "%d %d\n", width, height);
			    fprintf (fp, "%d\n", 255); /* current component type is RM_UNSIGNED_BYTE */
			    
			    /* write pixels */
			    for (i = 0; i < (width * height * 4); i += 4)
			    {
				fwrite ((pixels + i), 1, 3, fp);
			    }
			}
			break;
			
			default:
			{
			    rmError ("rmiWritePPM () : bogus type for writing.\n");
			}
			break;
		    }
		}
		break;
		    
		default:
		{
		    rmError ("rmiWritePPM () : bogus RMimage format for writing.\n");
		    fclose (fp);
		    return (RM_WHACKED);
		}
	    }
	    fclose (fp);
	}
    }
    else
    {
	sprintf (buffer, "rmiReadPPM () : cannot open file \"%s\" for output\n", filename);
	rmError (buffer);
	return (RM_WHACKED);
    }
    return (RM_CHILL);
}
