/*
 * 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: rmimage.c,v 1.8 2005/06/26 18:50:25 wes Exp $
 * Version: $Name: v180-alpha-02 $
 * $Revision: 1.8 $
 * $Log: rmimage.c,v $
 * Revision 1.8  2005/06/26 18:50:25  wes
 * Documentation updates.
 *
 * Revision 1.7  2005/05/15 15:22:11  wes
 * Update inline docs for rmImageMirror.
 *
 * Revision 1.6  2005/05/06 16:36:53  wes
 * Update docs to reflect that 1D images are supported. Only a couple
 * of minor code tweaks were needed.
 *
 * Revision 1.5  2005/02/19 16:32:31  wes
 * Distro sync and consolidation.
 * Add code to set "bytes per component" field of an RMimage.
 *
 * Revision 1.4  2005/01/23 17:04:03  wes
 * Copyright update to 2005.
 *
 * Revision 1.3  2004/01/16 16:45:12  wes
 * Updated copyright line for 2004.
 *
 * Revision 1.2  2003/02/02 02:07:15  wes
 * Updated copyright to 2003.
 *
 * Revision 1.1.1.1  2003/01/28 02:15:23  wes
 * Manual rebuild of rm150 repository.
 *
 * Revision 1.10  2003/01/16 22:21:17  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.9  2002/12/04 14:50:33  wes
 * Cleanup SGI compiles.
 *
 * Revision 1.8  2002/04/30 19:28:17  wes
 * rmImageSetVismap() now takes NULL for the vismap parameter. This change
 * allows applications to "clear" any vis colormap associated with an
 * RMimage, and is also used to fix a memory leak bug associated with
 * rmImageDelete.
 *
 * Revision 1.7  2001/10/15 01:38:53  wes
 * Bugs in 4-byte alignment code for W32 rmimages fixed.
 *
 * Revision 1.5  2001/05/28 23:29:29  wes
 * Fixed nbytesPerScanline computation bug in rmImageNew.
 *
 * Revision 1.4  2001/03/31 17:12:38  wes
 * v1.4.0-alpha-2 checkin.
 *
 * Revision 1.3  2000/12/03 22:35:38  wes
 * Mods for thread safety.
 *
 * Revision 1.2  2000/04/20 16:29:47  wes
 * Documentation additions/enhancements, some code rearragement.
 *
 * Revision 1.1.1.1  2000/02/28 21:29:40  wes
 * OpenRM 1.2 Checkin
 *
 * Revision 1.1.1.1  2000/02/28 17:18:48  wes
 * Initial entry - pre-RM120 release, source base for OpenRM 1.2.
 *
 */


#include <rm/rm.h>
#include "rmprivat.h"

/*
 * ----------------------------------------------------
 * @Name rmImageNew
 @pstart
 RMimage * rmImageNew (int ndims,
	               int width,
		       int height,
		       int depth,
		       RMenum formatEnum,
		       RMenum typeEnum,
		       RMenum copyFlag)
 @pend

 @astart
 int ndims - integer value specifying the number of dimensions in the
    new image. May be a value of 2 for regular images, 3 for 3D
    volumes, or 1 for 1D (texture) images (input).

 int width, height - integer values specifying the width and height of
    the new image (input).

 int depth - integer value specifying the size of the 3rd image
    dimension for 3D images. When "ndims" is 1 or 2, this parameter is
    ignored (input).

 RMenum formatEnum - an RMenum value specifying the pixel format for
    the new image. Use one of the following values (input):

    RM_IMAGE_ALPHA - alpha-only pixels, one component per pixel.
    RM_IMAGE_LUMINANCE - luminance only pixels, one component per pixel.
    RM_IMAGE_LUMINANCE_ALPHA - each pixel is considered to be a luminance-alpha
       pair, two components per pixel. Pixel data organized as LALALA...
    RM_IMAGE_RGB - each pixel is considered to be an RGB 3-tuple, organized
       as RGBRGB...
    RM_IMAGE_RGBA - each pixel is considered to be an RGBA 4-tuple, organized
       as RGBARGBA...
    RM_IMAGE_DEPTH - identifies the pixel data as depth buffer pixels. Each
       pixel consists of a single component.
    
 RMenum typeEnum - an RMenum value specifying the type of each pixel
    component.  Use one of the following enumerators (input):
    RM_UNSIGNED_BYTE, RM_FLOAT, RM_SHORT, or RM_UNSIGNED_SHORT.

 RMenum copyFlag - an RMenum value specifying whether or not RM will
    make a copy of the pixel data. Use one of RM_COPY_DATA or
    RM_DONT_COPY_DATA (input).
 @aend

 @dstart

 Creates a new RMimage object according to the caller's
 specifications, and returns an RMimage handle to the caller upon
 success, or NULL upon failure.

 Once created, the size and pixel format/type of an RMimage object may
 not be changed by the application. However, the pixel storage mode
 may change over the lifetime of the RMimage object.

 Win32 peculiarities: due to bugs in the Win32 OpenGL, all pixel data
 in an RMimage object is padded to 4-byte boundaries on a per-scanline
 basis. This will become significant when transferring pixel data to
 the RMimage object. Use the routine rmImageGetBytesPerScanline to
 obtain the computed number of bytes per scanline (that value is
 computed by RM by rmImageNew and remains constant for the lifetime of
 the RMimage object).

 When copyFlag is set to RM_COPY_DATA, rmImageNew creates a pixel
 buffer internal to the RMimage object. When set to RM_DONT_COPY_DATA,
 no pixel buffer is created. However, the bytes-per-scanline value is
 computed and assigned to the new RMimage object.

 Important! Applications must honor the bytes-per-scanline attribute
 of the RMimage object, otherwise the application's notion of pixel
 data organization will not jibe with RM's notion, leading potentially
 to garbage images on screen (at best) to possible seg faults (at
 worst).
 
 @dend
 * ----------------------------------------------------
 */
RMimage *
rmImageNew (int ndims,
	    int width,
	    int height,
	    int depth,
	    RMenum format_enum, /* RM_IMAGE_RGB, etc. */
	    RMenum type_enum,	/* RM_UNSIGNED_BYTE or RM_FLOAT */
	    RMenum copy_flag)
{
    int      elements;
    int      nbytes_per_component, nbytes_per_scanline, nbytes_for_pixeldata;
    RMimage *t;
    int      save;

    t = private_rmImageNew();

    save = t->compListIndx;
    memset(t, 0, sizeof(RMimage));
    t->compListIndx = save;
    
    if (t == NULL)
    {
	rmError("rmImageNew() error: unable to allocate a new RMimage object.");
	return(NULL);
    }

    private_rmImageSetNDims(t, ndims);
    private_rmImageSetType(t, type_enum);
    private_rmImageSetWidth(t, width);
    private_rmImageSetHeight(t, height);

    if ((ndims == 2) || (ndims == 1))
	depth = 1;

    private_rmImageSetDepth(t, depth);

    if ((elements = private_rmImageGetNumElements(format_enum)) == RM_WHACKED)
    {
	rmError("rmImage3DNew() error: unsupported image format requested.");
	return(NULL);
    }

    private_rmImageSetElements(t, elements);
    nbytes_per_component = private_rmImageNumComponentBytes(type_enum);
    private_rmImageSetBytesPerComponent(t, nbytes_per_component);
    nbytes_per_scanline = elements * width * nbytes_per_component;

#ifdef RM_WIN
    {
	int extraBytes;
	extraBytes = nbytes_per_scanline % 4;
	if (extraBytes != 0)
	{
	    extraBytes = 4 - extraBytes; 
	    nbytes_per_scanline += extraBytes;
#if 0
	    while (extraBytes > 0)
	    {
		nbytes_per_scanline++;
		extraBytes--;
	    }
#endif
	}
    }
#endif

    nbytes_for_pixeldata = nbytes_per_scanline * height * depth;
    private_rmImageSetBytesPerScanline(t, nbytes_per_scanline);
    t->pbsize = nbytes_for_pixeldata;

    private_rmImageSetFormat(t, format_enum);
    rmImageSetPixelZoom(t, 1.0F, 1.0F); /* default */

    rmImageSetScale(t, 1.0F);
    rmImageSetBias(t, 0.0F);
    
    if (copy_flag == RM_COPY_DATA)
    {
	t->pixeldata = (unsigned char *)malloc(sizeof(unsigned char) * nbytes_for_pixeldata);
	if (t == NULL)
	{
	    char buf[128];

	    sprintf(buf,"rmImage3DNew() error: unable to allocated %d bytes of voxel data.", nbytes_for_pixeldata);
	    rmError(buf);
	    return(NULL);
	}
	memset(t->pixeldata, 0, sizeof(unsigned char) * nbytes_for_pixeldata);
	private_rmImageSetCopyFlag(t, RM_COPY_DATA); 
    }
    else
    {
	private_rmImageSetCopyFlag(t, RM_DONT_COPY_DATA);
	t->pixeldata = NULL;
    }
#if 0
    /* 10/5/2000 - all RMimage display list code removed during SBIR work */
    private_rmImageSetDisplayList(t, -1);
#endif
    
    return(t);
}


/*
 * ----------------------------------------------------
 * @Name rmImageDup
 @pstart
 RMimage * rmImageDup (const RMimage *toDuplicate)
 @pend

 @astart
 const RMimage *toDuplicate - a handle to an RMimage object (input).
 @aend

 @dstart

 Use this routine to create a duplicate of an RMimage object. Returns
 a handle to the new object upon success, or NULL upon failure.

 A new RMimage object is created that is an exact duplicate of the
 input object. When the pixel data is under the purview of
 RM_COPY_DATA, a copy of the pixel data from the source RMimage is
 created and placed into the new RMimage object's pixel buffer.
 Otherwise (RM_DONT_COPY_DATA), the handle to the application pixel
 data is copied to the new RMimage object, along with the handle to
 the application callback used to free pixel data.

 The RMvisMap object, if present in source, is duplicated and copied
 to the new object. RMimage object's do not share data management of
 RMvisMap objects with the application.

 All size and other configuration information from the source is used
 to create the new object. Pixel zoom, bias and scale are also copied
 from the source to the new object.
 
 @dend
 * ----------------------------------------------------
 */
RMimage *
rmImageDup (const RMimage *src)
{
    unsigned char *s, *d;
    float          xz, yz;
    RMenum         copy_flag;
    RMimage       *dst;
    RMvisMap      *vmap = NULL;

    if (RM_ASSERT(src, "rmImageDup() error: input RMimage is NULL.") == RM_WHACKED)
	return(NULL);

    copy_flag = private_rmImageGetCopyFlag(src);

    /* make a new image which has all the config information present in the old image */
    dst = rmImageNew(private_rmImageGetNDims(src), private_rmImageGetWidth(src), private_rmImageGetHeight(src), private_rmImageGetDepth(src), 
		     private_rmImageGetFormat(src), private_rmImageGetType(src), private_rmImageGetCopyFlag(src));

    if (dst == NULL)
	return(NULL);
    
    rmImageGetPixelZoom(src, &xz, &yz);
    rmImageSetPixelZoom(dst, xz, yz);

    rmImageGetScale(src, &xz);
    rmImageSetScale(dst, xz);
    
    rmImageGetBias(src, &xz);
    rmImageSetBias(dst, xz);

    s = rmImageGetPixelData(src);
    d = rmImageGetPixelData(dst);

    if (private_rmImageGetCopyFlag(src) == RM_COPY_DATA)
        memcpy((void *)d, (void *)s, sizeof(unsigned char)*src->pbsize);
    else
    {
        dst->pixeldata = s;
	dst->appfreefunc = src->appfreefunc;
    }

    if (rmImageGetVismap(src, &vmap) == RM_CHILL)
    {
	rmImageSetVismap(dst, vmap);
	rmVismapDelete(vmap);
    }
    return(dst);
}


/*
 * ----------------------------------------------------
 * @Name rmImageDelete
 @pstart
 RMenum rmImageDelete (RMimage *toDelete)

 @pend

 @astart
 RMimage *toDelete - a handle to an RMimage object.
 @aend

 @dstart

 Releases resources associated with an RMimage object (the opposite of
 rmImageNew). Returns RM_CHILL upon success, or RM_WHACKED upon
 failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmImageDelete (RMimage *r)
{
    if (RM_ASSERT(r, "rmImageDelete() error: input RMimage is NULL.") == RM_WHACKED)
	return(RM_WHACKED);
    
    return(private_rmImageDelete(r));
#if 0
    /* this code goes away with the new component manager */
    if (private_rmImageGetCopyFlag(r) == RM_COPY_DATA)
	free((void *)(r->pixeldata));
    else
    {
	void (*appfunc)(void *);

	appfunc = r->appfreefunc;
	if (appfunc != NULL)
	    (*appfunc)((void *)(r->pixeldata));
    }
    free(r);
    return(RM_CHILL);
#endif
}


/*
 * ----------------------------------------------------
 * @Name rmImageSetPixelData
 @pstart
 RMenum rmImageSetPixelData (RMimage *toModify,
                             void *pixelData,
			     RMenum copyEnum,
			     void (*appFreeFunc)(void *))
 @pend

 @astart
 RMimage *toModify - a handle to an RMimage object (modified).

 void *pixelData - a handle to the raw pixel data supplied by the
    caller.  The contents of this buffer will become the pixel data
    for the RMimage object (input).

 RMenum copyEnum - an RMenum value specifying whether or not RM should
    make a copy of this data. Must be either RM_COPY_DATA or
    RM_DONT_COPY_DATA.

 void (*appFreeFunc)(void *) - a handle to an application callback
    used to free the pixel data when the RMimage object is
    deleted. When copyEnum is RM_COPY_DATA, this parameter is ignored.
 @aend

 @dstart

 Assigns raw pixel data to an RMimage object. Returns RM_CHILL upon
 success, or RM_WHACKED upon failure.

 The expected size of the pixel buffer is:

 (bytes_per_scanline)*height*depth * num_pixel_components

 The type of each pixel component may be obtained with rmImageGetType.

 The RMimage object's internal pixel buffer created with rmImageNew
 using RM_COPY_DATA should be considered READ ONLY. It is possible for
 applications to write directly into this pixel buffer, but this
 practice is highly discouraged. The reason is that RM will attempt to
 cache the RMimage object pixel buffer into OpenGL display lists
 wherever possible, and is capable of detecting changes to the pixel
 buffer (implying the need to refresh the display list data) only when
 the RM interface is used to update the pixel data
 (rmImageSetPixelData).

 In order to avoid multiple copies of pixel data in memory, use
 RM_DONT_COPY_DATA when creating the RMimage object. In that case, the
 application manages the pixel data. A call to rmImageSetPixelData
 will result in a pointer copy (not a block pixel copy), and will
 correctly cause any dependent display lists to be refreshed.

 When rmImageSetPixelData is called using RM_COPY_DATA, RM makes a
 copy of the pixel buffer and stores it in the RMimage object. When
 called with RM_DONT_COPY_DATA, RM copies only the pointer to the
 pixel data.

 When using RM_DONT_COPY_DATA, applications must provide a callback
 that will be invoked when the RMimage object is deleted with
 rmImageDelete.  The application callback will be passed a single
 parameter, a handle to the raw pixel data cast to a void *.
  
 @dend
 * ----------------------------------------------------
 */
RMenum
rmImageSetPixelData (RMimage *ri,
		     void *pixeldata,
		     RMenum copy_enum,
		     void (*appfreefunc)(void *))
{
    /*
     * validity checking:
     * 1. input image & pixeldata must not be null
     * 2. assert copy_enum == RM_DONT_COPY_DATA && appfreefunc != NULL
     */
    RMenum old_copy_flag;

    if (RM_ASSERT(ri, "rmImageSetPixelData() error: NULL input RMimage.") == RM_WHACKED)
	return(RM_WHACKED);

    if ((copy_enum == RM_DONT_COPY_DATA) && (appfreefunc == NULL))
    {
	rmError("rmImageSetPixelData() error: when using RM_DONT_COPY_DATA, you must supply a function which RM will call to free the image buffer. RM will not call this function until you delete the RMnode that contains the image (texture, sprite primitive, etc.)");
	return(RM_WHACKED);
    }
    /*
     * we need to check for the case in which the image was created with
     * RM_COPY_DATA, but now data is being set with RM_DONT_COPY_DATA. this
     * may be a potential problem..
     */
    old_copy_flag = private_rmImageGetCopyFlag(ri);
    if (old_copy_flag != copy_enum) /* a problem */
    {
	if (old_copy_flag == RM_COPY_DATA) /* & copy_flag == RM_DONT_COPY */
	    free((void *)(rmImageGetPixelData(ri))); /* free it */

	else	/* old is DONT_COPY, new is COPY, malloc buffer */
	{
	    unsigned char *c;
	    void         (*appfunc)(void *);

	    /* call the app free func, if defined, to free up the old buffer */
	    appfunc = ri->appfreefunc;

	    if (appfunc != NULL)
		(*appfunc)((void *)(rmImageGetPixelData(ri)));

	    c = (unsigned char *)malloc(sizeof(unsigned char) * ri->pbsize);
	    ri->pixeldata = c;
	}
    }
    /* else, no problem */
    private_rmImageSetCopyFlag(ri, copy_enum);
    
    if (copy_enum == RM_COPY_DATA)
    {
        memcpy((void *)(ri->pixeldata), pixeldata, ri->pbsize);
    }
    else
    {
        ri->pixeldata = (unsigned char *)pixeldata;
	ri->appfreefunc = appfreefunc;
    }
#if 0
    /* 10/5/200 - should be replaced with context cache stuff */
    private_rmImageSetDirtyFlag(ri, RM_TRUE); /* what's this for ? */
#endif
    
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmImageGetPixelData
 @pstart
 void * rmImageGetPixelData (const RMimage *toQuery)
 @pend

 @astart
 const RMimage *toQuery
 @aend

 @dstart

 Returns to the caller a handle to the raw pixel data inside an
 RMimage object, cast to a void * upon success. Otherwise, returns
 NULL.

 The size of the returned pixel buffer is:

 (bytes_per_scanline)*height*depth * num_pixel_components

 The type of each pixel component may be obtained with rmImageGetType.

 The RMimage object's internal pixel buffer created with rmImageNew
 using RM_COPY_DATA should be considered READ ONLY. It is possible for
 applications to write directly into this pixel buffer, but this
 practice is highly discouraged. The reason is that RM will attempt to
 cache the RMimage object pixel buffer into OpenGL display lists
 wherever possible, and is capable of detecting changes to the pixel
 buffer (implying the need to refresh the display list data) only when
 the RM interface is used to update the pixel data
 (rmImageSetPixelData).

 In order to avoid multiple copies of pixel data in memory, use
 RM_DONT_COPY_DATA when creating the RMimage object. In that case, the
 application manages the pixel data. A call to rmImageSetPixelData
 will result in a pointer copy (not a block pixel copy), and will
 correctly cause any dependent display lists to be refreshed.

 @dend
 * ----------------------------------------------------
 */
void *
rmImageGetPixelData (const RMimage *ri)
{
    if (RM_ASSERT(ri, "rmImageGetPixelData() error: input RMimage is NULL") == RM_WHACKED)
	return(NULL);
    
    return((void *)(ri->pixeldata));
}


/*
 * ----------------------------------------------------
 * @Name rmImageGetBytesPerScanline
 @pstart
 unsigned int rmImageGetBytesPerScanline (const RMimage *toQuery)
 @pend

 @astart
 const RMimage *toQuery - a handle to an RMimage object (input).
 @aend

 @dstart

 Returns to the caller the computed number of bytes per scanline of
 image data. Some OpenGL implementations silently require that all
 image data is padded to 4-byte boundaries on a per-scanline basis.

 This read-only value, bytes-per-scanline, is computed by rmImageNew
 at RMimage creation time and remains constant for the lifetime of the
 RMimage object.

 Note that there is no corresponding rmImageSetBytesPerScanline
 routine. The bytes per scanline of an RMimage object is established
 at the time the RMimage object is created with rmImageNew and remains
 immutable for the lifetime of the object.
 
 See also rmImageGetImageSize.

 @dend
 * ----------------------------------------------------
 */
unsigned int
rmImageGetBytesPerScanline (const RMimage *src)
{
    if (RM_ASSERT(src, "rmImageGetBytesPerScanline() error: input RMimage pointer is NULL.") == RM_WHACKED)
	return(0);
    
    return(private_rmImageGetBytesPerScanline(src));
}


/*
 * ----------------------------------------------------
 * @Name rmImageGetCopyFlag
 @pstart
 RMenum rmImageGetCopyFlag (const RMimage *toQuery)
 @pend

 @astart
 const RMimage *toQuery - a handle to an RMimage object object
    (input). 
 @aend

 @dstart

 Returns to the caller an RMenum value indicating the RMimage object's
 notion of who manages the pixel data. Either RM_COPY_DATA or
 RM_DONT_COPY_DATA will be returned, indicating that RM manages the
 data or the application manages the data, respectively.  If the input
 RMimage is NULL, then RM_WHACKED is returned.

 There is no corresponding rmImageSetCopyFlag routine.  The copy flag
 attribute is established at the time the RMimage is created with
 rmImageNew is called and is immutable for the lifetime of the object.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmImageGetCopyFlag (const RMimage *ri)
{
    if (RM_ASSERT(ri, "rmImageGetCopyFlag() error: the input RMimage is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

    return(private_rmImageGetCopyFlag(ri));
}


/*
 * ----------------------------------------------------
 * @Name rmImageGetType
 @pstart
 RMenum rmImageGetType (const RMimage *toQuery)
 @pend

 @astart
 const RMimage *toQuery
 @aend

 @dstart

 Returns to the caller an RMenum value indicating the RMimage pixel
 type.  Upon success, the RMenum value will be one of the RM pixel
 types, such as RM_UNSIGNED_BYTE, RM_FLOAT, RM_SHORT, or
 RM_UNSIGNED_SHORT. Otherwise, RM_WHACKED is returned.

 There is no corresponding rmImageSetType routine. The pixel type of
 an RMimage object is established at the time the RMimage object is
 created with rmImageNew, and remains immutable for the lifetime of
 the object.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmImageGetType (const RMimage *img)
{
    if (RM_ASSERT(img, "rmImageGetType() error: input RMimage pointer is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

    return(private_rmImageGetType(img));
}


/*
 * ----------------------------------------------------
 * @Name rmImageGetFormat
 @pstart
 RMenum rmImageGetFormat (const RMimage *toQuery)
 @pend

 @astart
 const RMimage *toQuery
 @aend

 @dstart

 Returns to the caller an RMenum value indicating the pixel format of
 the RMimage object. Upon success, the return value will be one of the
 RM pixel format enumerators, such as RM_IMAGE_ALPHA,
 RM_IMAGE_LUMINANCE, RM_IMAGE_LUMINANCE_ALPHA, RM_IMAGE_RGB,
 RM_IMAGE_RGBA or RM_IMAGE_DEPTH.  Upon failure, RM_WHACKED is
 returned to the caller.

 Note that there is no corresponding rmImageSetFormat routine. The
 pixel format of an RMimage object is established at the time the
 RMimage object is created with rmImageNew and remains immutable for
 the lifetime of the object.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmImageGetFormat (const RMimage *img)
{
    if (RM_ASSERT(img, "rmImageGetFormat() error: input RMimage pointer is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

    return(private_rmImageGetFormat(img));
}


/*
 * ----------------------------------------------------
 * @Name rmImageGetImageSize
 @pstart
 RMenum rmImageGetImageSize (const RMimage *toQuery,
                             int *returnNDims,
		             int *returnWidth,
			     int *returnHeight,
			     int *returnDepth,
			     int *returnElements,
			     int *returnBytesPerScanline)

 @pend

 @astart
 const RMimage *toQuery - a handle to an RMimage object to query
    (input).

 int *returnNDims, *returnWidth, *returnHeight, *returnDepth,
    *returnElements, *returnBytesPerScanline - handles to integers
    supplied by the caller. Parameters from the RMimage object will be
    copied into these caller-supplied memory areas. Specifying NULL
    for one or more of these values is permissible.
 @aend

 @dstart

 Use rmImageGetImageSize to obtain configuration information about an
 RMimage object.

 If not NULL, upon return, "returnNDims" will contain the number of
 dimensions in the RMimage object, either 2 or 3.

 If not NULL, upon return, "returnWidth", "returnHeight" and
 "returnDepth" will contain the width, height and depth of the RMimage
 object. When the dimensionality of the RMimage object is 2,
 "returnDepth" will be set to 1.

 If not NULL, upon return, "returnElements" will contain the number of
 components per pixel. For example, if the pixel format is
 RM_IMAGE_RGB, "returnElements" will contain 3.

 If not NULL, upon return, "returnBytesPerScanline" will contain the
 value computed by RM indicating the actual number of bytes per
 scanline in the pixel buffer. That value is computed to honor
 scanline padding requirements of some OpenGL implementations. This
 read-only value is computed by rmImageNew.

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 Note that there is no corresponding rmImageSetImageSize routine. The
 size of an RMimage object is established at the time the RMimage
 object is created with rmImageNew and remains immutable for the
 lifetime of the object.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmImageGetImageSize (const RMimage *ri,
		     int *ret_ndims,
		     int *ret_w,
		     int *ret_h,
		     int *ret_d,
		     int *ret_e,
		     int *bytes_per_scanline)
{
    if (RM_ASSERT(ri, "rmImageGetImageSize() error: input RMimage is NULL.") == RM_WHACKED)
	return(RM_WHACKED);
    
    /*
     * we assume that the caller may or may not be interested in all the
     * parms, therefore it's not an error for some of the parms to be NULL.
     */
    if (ret_ndims != NULL)
	*ret_ndims = private_rmImageGetNDims(ri);

    if (ret_w != NULL)
	*ret_w = private_rmImageGetWidth(ri);

    if (ret_h != NULL)
	*ret_h = private_rmImageGetHeight(ri);

    if (ret_d != NULL)
	*ret_d = private_rmImageGetDepth(ri);

    if (ret_e != NULL)
	*ret_e = private_rmImageGetElements(ri);

    if (bytes_per_scanline != NULL)
	*bytes_per_scanline = private_rmImageGetBytesPerScanline(ri);
    
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmImageSetPixelZoom
 @pstart
 RMenum rmImageSetPixelZoom (RMimage *toModify,
		             float xzoom,
		             float yzoom)
 @pend

 @astart
 RMimage *toModify - a handle to an RMimage object (modified).

 float xzoom, yzoom - floating point values used to specify the x- and y-axis
    pixel replication, or zoom, factors (input).
 @aend

 @dstart

 This routine is used to set the x- and y-axis pixel zoom parameters
 of an RMimage object.  Returns RM_CHILL upon success, or RM_WHACKED
 upon failure.
 
 In OpenGL, image-based data is usually drawn pixel-for-pixel, so that
 one pixel in the source image maps to one pixel on the screen. That
 mapping can be modified by changing the "pixel zoom". Setting the
 x-axis pixel zoom to 2.0 will cause each input pixel to map to two
 on-screen pixels.  Setting the x-axis pixel zoom to 0.5 will cause
 each two pixels of input to map to one pixel of output.

 Pixel zoom operations apply to RM_SPRITE primitives, and background
 image tiles. The pixel zoom values are not used when the RMimage
 object is the source of pixel data in an RMtexture (used in texture
 mapping).

 @dend
 * ----------------------------------------------------
 */
RMenum
rmImageSetPixelZoom (RMimage *ri,
		     float xzoom,
		     float yzoom)
{
    if (RM_ASSERT(ri, "rmImageSetPixelZoom() error: input RMimage pointer is NULL.") == RM_WHACKED)
	return(RM_WHACKED);
    
    ri->xzoom = xzoom;
    ri->yzoom = yzoom;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmImageGetPixelZoom
 @pstart
 RMenum rmImageGetPixelZoom (const RMimage *toQuery,
		             float *returnXZoom,
		             float *returnYZoom)
 @pend

 @astart
 RMimage *toQuery - a handle to an RMimage object that will be queried
    by this routine (input).

 float *returnXZoom, *returnYZoom - handles to floating point values
    that will contain the x- and y-axis pixel zoom factors of the
    RMimage object toQuery (modified).
 @aend

 @dstart

 This routine is used to obtain the x- and y-axis pixel zoom
 parameters of an RMimage object. The pixel zoom factors are copied
 into caller-supplied memory.  Returns RM_CHILL upon success, or
 RM_WHACKED upon failure.
 
 In OpenGL, image-based data is usually drawn pixel-for-pixel, so that
 one pixel in the source image maps to one pixel on the screen. That
 mapping can be modified by changing the "pixel zoom". Setting the
 x-axis pixel zoom to 2.0 will cause each input pixel to map to two
 on-screen pixels.  Setting the x-axis pixel zoom to 0.5 will cause
 each two pixels of input to map to one pixel of output.

 Pixel zoom operations apply to RM_SPRITE primitives, and background
 image tiles. The pixel zoom values are not used when the RMimage
 object is the source of pixel data in an RMtexture (used in texture
 mapping).

 @dend
 * ----------------------------------------------------
 */
RMenum
rmImageGetPixelZoom (const RMimage *ri,
		     float *xzoom,
		     float *yzoom)
{
    if (RM_ASSERT(ri, "rmImageGetPixelZoom() error: the input RMimage object is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

    if (RM_ASSERT(xzoom, "rmImageGetPixelZoom() error: the input xzoom parameter pointer is NULL.") == RM_WHACKED)
	return(RM_WHACKED);
	
    if (RM_ASSERT(yzoom, "rmImageGetPixelZoom() error: the input yzoom parameter pointer is NULL.") == RM_WHACKED)
	return(RM_WHACKED);
	
    *xzoom = ri->xzoom;
    *yzoom = ri->yzoom;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmImageSetVismap
 @pstart
 RMenum rmImageSetVismap (RMimage *toModify,
		          const RMvisMap *vismap)
 @pend

 @astart
 RMimage *toModify - a handle to an RMimage object (modified).

 const RMvisMap *vismap - a handle to an RMvisMap object (input).
 @aend

 @dstart

 Use this routine to set, or delete, the colormap associated with an
 RMimage object.  This routine will return RM_CHILL upon success, or
 RM_WHACKED upon failure. A  copy of the caller's RMvisMap object is
 created inside this routine, and assigned to the RMimage object. If the
 input RMvisMap parameter is NULL, any RMvisMap associated with the
 RMimage object will be deleted.  Application changes to the
 application RMvisMap object made after this call will not affect the
 RMimage object.

 The colormap is used at pixel expansion time within the OpenGL pixel
 pipeline. Inside OpenGL, application pixel components are first
 converted to the range [0..1] using an algorithm that is a function
 of the source pixel type (byte, short, etc.). Next, pixels are
 converted from the source pixel format (luminance-alpha, RGB, etc.)
 into RGBA. Next, each pixel component is multiplied by the "scale"
 factor, then "bias" is added. Finally, each pixel component undergoes
 a color->color lookup.

 This routine is used to control that color->color lookup. Pixel
 scale, bias and color lookup operations are applied to all imaging
 operations, including both pixel draws and during transfer of pixel
 data to texture memory. Therefore, this routine may be used to
 perform false-coloring of scalar image data either for direct display
 using an RM_SPRITE primitive or in the form of texture-mapping.

 Note that scale and bias parameters operate on pixel data that has
 been converted to the range [0..1] according to a type-specific
 algorithm.  Source pixels that are in floating point are not
 converted, but are clamped to the range [0..1]. Clamping occurs late
 in the pixel pipeline, only after scale, bias and color lookup have
 been performed.

 False coloring with the RMvisMap object is implemented by
 manipulating OpenGL pixel transfer modes with glPixelTransferi().
 
 January 2000 - RMvisMap objects contain a transfer function. The
 transfer function is effectively ignored during the false coloring
 process inside the OpenGL pixel pipeline. Use the scale and bias
 attributes of the RMimage object to implement a transfer function.
 However, the RMvisMap object's transfer function is used inside the
 RMV visualization routines.

 April 2002 - NULL is now permitted as a parameter for the vismap.
 this will permit applications to "clear" the vismap associated with
 an RMimage object.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmImageSetVismap (RMimage *dst,
		  const RMvisMap *vismap)
{
    if (RM_ASSERT(dst, "rmImageSetVismap() error: input RMimage object is NULL") == RM_WHACKED) 
	return(RM_WHACKED);

    if (dst->vismap)
    {
	rmVismapDelete(dst->vismap);
	dst->vismap = NULL;
    }

    if (vismap != NULL)
	dst->vismap = rmVismapDup(vismap);
    
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmImageGetVismap
 @pstart
 RMenum rmImageGetVismap (const RMimage *toQuery,
		          RMvisMap **vismapReturn)
 @pend

 @astart
 const RMimage *toQuery - a handle to an RMimage object (input).

 RMvisMap **vismapReturn - a handle to an RMvisMap pointer (modified).
 @aend

 @dstart

 Use this routine to set the obtain a copy of the colormap associated
 with an RMimage object. If a RMvisMap object exists inside the
 RMimage object, a copy of the RMvisMap object will be created and
 returned to the caller, and RM_CHILL will be returned. Otherwise,
 RM_WHACKED is returned.

 The RMvisMap object returned to the caller is a duplicate of the one
 inside the RMimage object. Application changes to the returned
 RMvisMap will not affect the RMimage object. When applications are
 finished with the returned RMvisMap object, resources should be freed
 with rmVismapDelete().

 The colormap is used at pixel expansion time within the OpenGL pixel
 pipeline. Inside OpenGL, application pixel components are first
 converted to the range [0..1] using an algorithm that is a function
 of the source pixel type (byte, short, etc.). Next, pixels are
 converted from the source pixel format (luminance-alpha, RGB, etc.)
 into RGBA. Next, each pixel component is multiplied by the "scale"
 factor, then "bias" is added. Finally, each pixel component undergoes
 a color->color lookup.

 This routine is used to control that color->color lookup. Pixel
 scale, bias and color lookup operations are applied to all imaging
 operations, including both pixel draws and during transfer of pixel
 data to texture memory. Therefore, this routine may be used to
 perform false-coloring of scalar image data either for direct display
 using an RM_SPRITE primitive or in the form of texture-mapping.

 Note that scale and bias parameters operate on pixel data that has
 been converted to the range [0..1] according to a type-specific
 algorithm.  Source pixels that are in floating point are not
 converted, but are clamped to the range [0..1]. Clamping occurs late
 in the pixel pipeline, only after scale, bias and color lookup have
 been performed.

 False coloring with the RMvisMap object is implemented by
 manipulating OpenGL pixel transfer modes with glPixelTransferi().
 
 January 2000 - RMvisMap objects contain a transfer function. The
 transfer function is effectively ignored during the false coloring
 process inside the OpenGL pixel pipeline. Use the scale and bias
 attributes of the RMimage object to implement a transfer function.
 However, the RMvisMap object's transfer function is used inside the
 RMV visualization routines.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmImageGetVismap (const RMimage *toQuery,
		  RMvisMap **vismapReturn)
{
    RMenum rstat;
    
    if ((RM_ASSERT(toQuery, "rmImageGetVismap() error: input RMimage object is NULL") == RM_WHACKED) ||
	(RM_ASSERT(vismapReturn, "rmImageGetVismap() error: input vismap is NULL.") == RM_WHACKED))
	return(RM_WHACKED);

    if (toQuery->vismap)
    {
	*vismapReturn = rmVismapDup(toQuery->vismap);
	rstat = RM_CHILL;
    }
    else
	rstat = RM_WHACKED;

    return(rstat);
}


/*
 * ----------------------------------------------------
 * @Name rmImageMirror
 @pstart
 RMenum rmImageMirror (RMimage *toMirror,
	               RMenum mirrorAxisEnum)
 @pend

 @astart
 RMimage *toMirror - a handle to an RMimage object (modified).

 RMenum mirrorAxisEnum - an RMenum value specifying along which axis
    mirroring will occur (input). 
 @aend

 @dstart

 This routine is used to perform reflection of pixel data in an
 RMimage object. Returns RM_CHILL upon success, or RM_WHACKED upon
 failure.

 The parameter mirrorAxisEnum should be either RM_IMAGE_MIRROR_HEIGHT to
 flip the image along the height axis (exchanging the top and bottom), or
 RM_IMAGE_MIRROR_WIDTH to flip  the image along the horizontal axis
 (exchanging left and right).

 May 2005 - at this time, this routine will operate only on 2D images.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmImageMirror (RMimage *toMirror,
	       RMenum mirrorAxisEnum)
{
    int ndim;
    
    if (RM_ASSERT(toMirror,"rmImageMirror() error: the input RMimage object is NULL. ") == RM_WHACKED)
	return(RM_WHACKED);

    rmImageGetImageSize(toMirror, &ndim, NULL, NULL, NULL, NULL, NULL);
    if (ndim != 2)
    {
	rmWarning(" rmImageMirror() warning: can handle only 2D images at this time. ");
	return(RM_WHACKED);
    }

    if (mirrorAxisEnum == RM_IMAGE_MIRROR_HEIGHT)
	return(private_rmImage2DMirrorVertical(toMirror));
    else /* assume RM_IMAGE_MIRROR_WIDTH */
	return(private_rmImage2DMirrorHorizontal(toMirror));
}

			
/*
 * ----------------------------------------------------
 * @Name rmImageResize
 @pstart
 RMenum rmImageResize (const RMimage *src,
	               RMimage *dst,
	               RMenum hardwareEnum,
		       RMpipe *hwPipe)
 @pend

 @astart
 const RMimage *src - a handle to an RMimage object. Used as the
    source image in an image resize operation.

 RMimage *dst - a handle to an RMimage object. The resized image will
    be placed into this object.

 RMenum hardwareEnum - an RMenum value specifying whether or not to
    use the graphics hardware for performing the pixel resize
    operation.  Must be either RM_HARDWARE or RM_SOFTWARE.

 RMpipe *hwPipe - a handle to an RMpipe (input). This parameter must be
    non-NULL whenever the hardwareEnum parameter is set to RM_HARDWARE,
    and must refer to a fully initialized RMpipe. When RM_SOFTWARE
    image resizing is specified, this parameter is ignored, and may
    be set to NULL by the caller.
 @aend

 @dstart

 Use this routine to scale an image. The source image is scaled to
 match the dimensions specified by the destination image. Thus, the
 destination image must have been first created with rmImageNew,
 implying that it's size, pixel format and type attributes have been
 set. This routine does not create a new image. This routine is useful
 for resizing images to be an even power of 2 in size, as required for
 OpenGL texture mapping.

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 When hardwareEnum is set to RM_SOFTWARE, the routine gluScaleImage is
 used to perform the image resize operation, therefore it is a
 software operation. Note that gluScaleImage queries the underlying
 OpenGL for information about unpacklength.

 Therefore, OpenGL must be fully initialized prior to making a call to
 rmImageResize using either RM_HARDWARE or RM_SOFTWARE. 

 When hardwareEnum is set to RM_HARDWARE, the underlying graphics
 hardware is used to perform the resizing operation. This is
 accomplished by writing pixels to the framebuffer using appropriate
 pixel zoom values, then reading the image back into the RMimage pixel
 data memory. In many cases, use of RM_HARDWARE will greatly
 accelerate image resizing operations, such as resizing a giga-scale
 volume down to something more manageable.

 A valid, initialized RMpipe is required in the hwPipe parameter when
 using RM_HARDWARE image resize methods.

 During RM_HARDWARE image resize operations, pixels are written into
 the back buffer, then read back. After the readback, a swapbuffers
 is performed to provide visual feedback. Since the backbuffer is used
 for write/read operations, the display window need not be on top in
 stacking order.

 When using RM_HARDWARE, the size of the window used by the hwPipe
 RMpipe must be greater than or equal in size to the size of the
 destination image dimensions. If this condition does not hold, an
 error message is issued, and RM_WHACKED is returned.

 Note about RM_HARDWARE and using RM_PIPE_MULTISTAGE_PARALLEL RMpipe
 processing modes: use of RM_HARDWARE in conjunction with
 RM_PIPE_MULTISTAGE_PARALLEL is guaranteed to work only if the image
 resize operation is performed before the first rmFrame() call. After
 the first rmFrame() call, an image resize operation that uses RM_HARDWARE
 on an RMpipe using the RM_PIPE_MULTISTAGE_PARALLEL processing mode
 may produce unexpected results, including an application crash.

 As of the time of this writing (January 2000), only RM_HARDWARE is
 supported for 3D RMimage objects.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmImageResize (const RMimage *src,
	       RMimage *dst,
	       RMenum hardwareEnum,
	       RMpipe *hwPipe)
{
    /*
     * hack. for now, we figure out if the images are 2D or 3D, and
     * call the appropriate resize routine.
     */
    int    src_ndim, dst_ndim, destW, destH, destD;
    RMenum rstat;
    /*
     * validity checking:
     * 1. src & dst are not NULL.
     * 2. image formats for both are the same
     * 3. image ndim for both are the same
     */

    if ((RM_ASSERT(src, "rmImageResize() error: input src RMimage is NULL.") == RM_WHACKED) ||
	(RM_ASSERT(dst, "rmImageResize() error: input dst RMimage is NULL.") == RM_WHACKED))
	return(RM_WHACKED);

    /* we want better validity checking on hwPipe than this. */
    if ((hardwareEnum == RM_HARDWARE) && (hwPipe == NULL))
    {
	rmError("rmImageResize() error: a valid RMpipe object must be provided when using RM_HARDWARE as the image resize method.");
	return(RM_WHACKED);
    }

    rmImageGetImageSize(src, &src_ndim, NULL, NULL, NULL, NULL, NULL);
    rmImageGetImageSize(dst, &dst_ndim, &destW, &destH, &destD, NULL, NULL);

    if (src_ndim != dst_ndim)
    {
	rmError("rmImageResize() error: ndim for src and dst images are not equal.");
	return(RM_WHACKED);
    }

    /* for RM_HARDWARE, check if size of dst image is less than or
       equal to the size of the display window. */
    if (hardwareEnum == RM_HARDWARE)
    {
	int winW, winH;
	rmPipeGetWindowSize(hwPipe, &winW, &winH);
	if ((winW < destW) || (winH < destH))
	{
	    rmError(" rmImageResize() error: when using RM_HARDWARE image resize methods, the destination image dimensions must be less than or equal to the size of the display window.");
	    return(RM_WHACKED);
	}
    }

#if 0
    if (private_rmImageGetFormat(src) != private_rmImageGetFormat(dst))
    {
	rmError("rmImageResize() error: src and dst images are not the same image format.");
	return(RM_WHACKED);
    }
#endif
    
    if (private_rmImageGetNDims(src) == 2)
	rstat = private_rmImage2DResize(src, dst, hardwareEnum, hwPipe);
    else 
       if (private_rmImageGetNDims(src) == 3)
	  rstat = private_rmImage3DResize(src, dst, hardwareEnum, hwPipe);
       else
	  rstat = RM_WHACKED; /* bogus image dimensions */

    return(rstat);
}


/*
 * ----------------------------------------------------
 * @Name rmImageSetScale
 @pstart
 RMenum rmImageSetScale (RMimage *toModify,
		         float newScale)

 @pend

 @astart
 RMimage *toModify - a handle to an RMimage object to modify.

 float newScale - a floating point value that represents the new pixel
    scale that will be applied to the RMimage object.
 @aend

 @dstart

 Use this routine to set the pixel scale parameter of an RMimage
 object.  Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 Pixel scale and bias are used in the OpenGL pixel pipeline to modify
 pixel values as they move from the application into the framebuffer.
 Inside OpenGL, application pixel components are first converted to
 the range [0..1] using an algorithm that is a function of the source
 pixel type (byte, short, etc.). Next, pixels are converted from the
 source pixel format (luminance-alpha, RGB, etc.) into RGBA. Next,
 each pixel component is multiplied by the "scale" factor, then "bias"
 is added.

 The scale value is simply a linear multiplier that is applied to each
 pixel value.

 Note that scale and bias parameters operate on pixel data that has
 been converted to the range [0..1] according to a type-specific
 algorithm.  Source pixels that are in floating point are not
 converted, but are clamped to the range [0..1]. Clamping occurs late
 in the pixel pipeline, only after scale, bias and color lookup have
 been performed.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmImageSetScale (RMimage *toModify,
		 float newScale)
{
    if (RM_ASSERT(toModify, "rmImageSetScale error: the input RMimage is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    toModify->scale = newScale;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmImageGetScale
 @pstart
 RMenum rmImageGetScale (const RMimage *toQuery,
		         float *returnScale)

 @pend

 @astart
 const RMimage *toQuery - a handle to an RMimage object to query
    (input). 

 float *returnScale - a handle to a float that will be set to contain
    the RMimage object's pixel scale attribute (modified).
 @aend

 @dstart

 Use this routine to obtain the pixel scale parameter of an RMimage
 object.  Returns RM_CHILL upon success, and copies the RMimage
 object's pixel scale attribute into the caller-supplied
 memory. Returns RM_WHACKED upon failure.

 Pixel scale and bias are used in the OpenGL pixel pipeline to modify
 pixel values as they move from the application into the framebuffer.
 Inside OpenGL, application pixel components are first converted to
 the range [0..1] using an algorithm that is a function of the source
 pixel type (byte, short, etc.). Next, pixels are converted from the
 source pixel format (luminance-alpha, RGB, etc.) into RGBA. Next,
 each pixel component is multiplied by the "scale" factor, then "bias"
 is added.

 The scale value is simply a linear multiplier that is applied to each
 pixel value.
 
 Note that scale and bias parameters operate on pixel data that has
 been converted to the range [0..1] according to a type-specific
 algorithm.  Source pixels that are in floating point are not
 converted, but are clamped to the range [0..1]. Clamping occurs late
 in the pixel pipeline, only after scale, bias and color lookup have
 been performed.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmImageGetScale (const RMimage *toQuery,
		 float *returnScale)
{
    if ((RM_ASSERT(toQuery, "rmImageGetScale error: the input RMimage is NULL") == RM_WHACKED) ||
	(RM_ASSERT(returnScale, "rmImageGetScale error: the returnScale float * is NULL") == RM_WHACKED))
	return(RM_WHACKED);

    *returnScale = toQuery->scale;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmImageSetBias
 @pstart
 RMenum rmImageSetBias (RMimage *toModify,
		        float newBias)

 @pend

 @astart
 RMimage *toModify - a handle to an RMimage object (modified).

 float newBias - a floating point value that represents the new pixel
    bias that will be applied to the RMimage object (input).
 @aend

 @dstart

 Use this routine to set the pixel bias parameter of an RMimage
 object.  Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 Pixel scale and bias are used in the OpenGL pixel pipeline to modify
 pixel values as they move from the application into the framebuffer.
 Inside OpenGL, application pixel components are first converted to
 the range [0..1] using an algorithm that is a function of the source
 pixel type (byte, short, etc.). Next, pixels are converted from the
 source pixel format (luminance-alpha, RGB, etc.) into RGBA. Next,
 each pixel component is multiplied by the "scale" factor, then "bias"
 is added.

 The bias value is simply an offset that is added to each pixel value.
 
 Note that scale and bias parameters operate on pixel data that has
 been converted to the range [0..1] according to a type-specific
 algorithm.  Source pixels that are in floating point are not
 converted, but are clamped to the range [0..1]. Clamping occurs late
 in the pixel pipeline, only after scale, bias and color lookup have
 been performed.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmImageSetBias (RMimage *toModify,
	        float newBias)
{
    if (RM_ASSERT(toModify, "rmImageSetBias error: the input RMimage is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    toModify->bias = newBias;

    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmImageGetBias
 @pstart
 RMenum rmImageGetBias (const RMimage *toQuery,
		        float *returnBias)

 @pend

 @astart
 const RMimage *toQuery - a handle to an RMimage object to query
    (input).

 float *returnBias - a handle to a float that will be set to contain
    the RMimage object's bias attribute (modified).
 @aend

 @dstart

 Use this routine to obtain the pixel bias parameter of an RMimage
 object.  Returns RM_CHILL upon success, and copies the RMimage
 object's bias attribute into the caller-supplied memory. Returns
 RM_WHACKED upon failure.

 Pixel scale and bias are used in the OpenGL pixel pipeline to modify
 pixel values as they move from the application into the framebuffer.
 Inside OpenGL, application pixel components are first converted to
 the range [0..1] using an algorithm that is a function of the source
 pixel type (byte, short, etc.). Next, pixels are converted from the
 source pixel format (luminance-alpha, RGB, etc.) into RGBA. Next,
 each pixel component is multiplied by the "scale" factor, then "bias"
 is added.

 The bias value is simply an offset that is added to each pixel value.
 
 Note that scale and bias parameters operate on pixel data that has
 been converted to the range [0..1] according to a type-specific
 algorithm.  Source pixels that are in floating point are not
 converted, but are clamped to the range [0..1]. Clamping occurs late
 in the pixel pipeline, only after scale, bias and color lookup have
 been performed.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmImageGetBias (const RMimage *toQuery,
		float *returnBias)
{
    if ((RM_ASSERT(toQuery, "rmImageGetBias error: the input RMimage is NULL") == RM_WHACKED) ||
	(RM_ASSERT(returnBias, "rmImageGetBias error: the returnBias float * is NULL") == RM_WHACKED))
	return(RM_WHACKED);

    *returnBias = toQuery->bias;

    return(RM_CHILL);
}


/* PRIVATE */
int
private_rmImageNumComponentBytes (RMenum type_enum)
{
    int rval = 0;

    switch(type_enum)
       {
       case RM_UNSIGNED_BYTE:
	  rval = sizeof(unsigned char);
	  break;
	  
       case RM_FLOAT:
	  rval = sizeof(float);
	  break;
	  
       case RM_UNSIGNED_SHORT:
       case RM_SHORT:
	  rval = sizeof(short);
	  break;
	  
       default:
	  rmError("private_rmImageNumComponentBytes() error: invalid image type enumerator.");
	  break;
       }

    return(rval);
}


/* PRIVATE */
GLenum
private_rmImageGetOGLFormat (const RMimage *img)
{
    int glformat;
    
    switch(private_rmImageGetFormat(img))
       {
       case RM_IMAGE_ALPHA:
	  glformat = GL_ALPHA;
	  break;

       case RM_IMAGE_LUMINANCE_ALPHA:
	  glformat = GL_LUMINANCE_ALPHA;
	  break;
	  
       case RM_IMAGE_LUMINANCE:
	  glformat = GL_LUMINANCE;
	  break;
	  
#if 0
	  /* deprecated */
       case RM_IMAGE_INDEXED_RGB:
#endif
       case RM_IMAGE_RGB:
	  glformat = GL_RGB;
	  break;

#if 0
	  /* deprecated */
       case RM_IMAGE_INDEXED_RGBA:
#endif
       case RM_IMAGE_RGBA:
	  glformat = GL_RGBA;
	  break;
	  
       case RM_IMAGE_DEPTH:
	  glformat = GL_DEPTH_COMPONENT;
	  break;
	  
       default:
	  rmError(" rmImageGetOGLFormat() error: image format not appropriately handled. ");
	  glformat = GL_RGB; /* ???? */
	  break;
       }
    return(glformat);
}


/* PRIVATE */
GLenum
private_rmImageGetOGLType(const RMimage *img)
{
    int glformat;
    
    switch(private_rmImageGetType(img))
       {
       case RM_UNSIGNED_BYTE:
	  glformat = GL_UNSIGNED_BYTE;
	  break;

       case RM_FLOAT:
	  glformat = GL_FLOAT;
	  break;

       case RM_SHORT:
	  glformat = GL_SHORT;
	  break;

       case RM_UNSIGNED_SHORT:
	  glformat = GL_UNSIGNED_SHORT;
	  break;

       default:
	  rmError(" rmImageGetOGLType() error: image format not appropriately handled. ");
	  glformat = GL_UNSIGNED_BYTE; /* ???? */
	  break;
       }
    return(glformat);
}


/* PRIVATE */
int
private_rmImageGetNumElements (RMenum image_format_enum)
{
    int rstat;

    /* given an RM image format enumerator, return the number of elements that make up the image */
    switch(image_format_enum)
       {
#if 0
	  /* deprecated */
       case RM_IMAGE_INDEXED_RGB:
       case RM_IMAGE_INDEXED_RGBA:
#endif
       case RM_IMAGE_LUMINANCE:
       case RM_IMAGE_ALPHA:
       case RM_IMAGE_DEPTH:
	  rstat = 1;
	  break;
	  
       case RM_IMAGE_RGB:
	  rstat = 3;
	  break;
	  
       case RM_IMAGE_RGBA:
	  rstat = 4;
	  break;
	  
       case RM_IMAGE_LUMINANCE_ALPHA:
	  rstat = 2;
	  break;
	  
       default:
	  rstat = RM_WHACKED;
	  break;
       }
    return(rstat);
}


/* PRIVATE */
RMenum
private_rmImage2DMirrorVertical (RMimage *r)
{
    unsigned char *tmp, *s, *d;
    int            w, h, i, n, elements;
    RMenum         f;

    if (RM_ASSERT(r, "rmImage2DMirrorVert() error: input RMimage2D is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

    w = private_rmImageGetWidth(r);
    h = private_rmImageGetHeight(r);

    f = private_rmImageGetFormat(r);
    elements = private_rmImageGetElements(r);
    
    n = private_rmImageGetBytesPerScanline(r);
    
    tmp = (unsigned char *)malloc(sizeof(unsigned char)*n);

    s = rmImageGetPixelData(r);

    if (RM_ASSERT(s, "private_rmImage2DMirrorVertical() error: the pixel data pointer for the input RMimage is NULL!") == RM_WHACKED)
	return(RM_WHACKED);
    
    d = s + (n * (h - 1));
    
    for (i = 0; i< (h >> 1); i++, s += n, d -= n)
    {
	memcpy((void *)tmp, (void *)d, (sizeof(unsigned char) * n));
	memcpy((void *)d, (void *)s, (sizeof(unsigned char) * n));
	memcpy((void *)s, (void *)tmp, (sizeof(unsigned char) * n));
    }
    free((void *)tmp);

    return(RM_CHILL);
}

/* private */
static void
private_rmMirrorHorizontalPixels(unsigned char *b,
				 int w,
				 int bytesPerPixel,
				 unsigned char *b2)
{
    int i;
    int s = 0;
    int d = (w-1) * bytesPerPixel;

    for (i=0;i<w;i++)
    {
	/* copy pixel[i] in b1 to pixel[w-1-i] in b2 */
	memcpy((void *)(b2+d), (void *)(b+s), bytesPerPixel);
	s += bytesPerPixel;
	d -= bytesPerPixel;
    }

    /* copy b2 back into b */
    memcpy(b, b2, sizeof(unsigned char)*w*bytesPerPixel); 
}

/* PRIVATE */
RMenum
private_rmImage2DMirrorHorizontal (RMimage *r)
{
    unsigned char *tmp, *s, *tmp2;
    int            w, h, i, n, elements;
    RMenum         f, t;
    int            bytesPerPixel;

    if (RM_ASSERT(r, "rmImage2DMirrorVert() error: input RMimage2D is NULL.") == RM_WHACKED)
	return(RM_WHACKED);

    w = private_rmImageGetWidth(r);
    h = private_rmImageGetHeight(r);

    f = private_rmImageGetFormat(r);
    elements = private_rmImageGetElements(r);

    t = private_rmImageGetType(r);
    
    n = private_rmImageGetBytesPerScanline(r);
    
    tmp = (unsigned char *)malloc(sizeof(unsigned char)*n);
    tmp2 = (unsigned char *)malloc(sizeof(unsigned char)*n);

    bytesPerPixel = private_rmImageNumComponentBytes(t) * elements;

    s = rmImageGetPixelData(r);

    if (RM_ASSERT(s, "private_rmImage2DMirrorVertical() error: the pixel data pointer for the input RMimage is NULL!") == RM_WHACKED)
	return(RM_WHACKED);
    
    for (i = 0; i<h; i++, s += n)
    {
	/* copy row of pixels into work buffer */
	memcpy((void *)tmp, (void *)s, (sizeof(unsigned char) * n));

	/* reverse order of pixels in tmp */
	private_rmMirrorHorizontalPixels(tmp, w, bytesPerPixel, tmp2);

	/* copy row of pixels from work buffer back into pixel buffer */
	memcpy((void *)s, (void *)tmp, (sizeof(unsigned char) * n));
    }
    free((void *)tmp2);
    free((void *)tmp);

    return(RM_CHILL);
}


/* PRIVATE */
RMenum
private_rmImage2DResize (const RMimage *src,
			 RMimage *dst,
			 RMenum hardware_or_software,
			 RMpipe *hwPipe)
{
    /*
     * resizes the image in src placing the result in dst. we
     * restrict the image format of src and dst to be the same.
     *
     * we assume that the image buffer in dst has already been malloc'ed
     *
     * 7/27/01 - RM_HARDWARE operations (read, write) moved to the
     * back buffer to avoid problems associated with needing the OpenGL
     * window to be unobstructed and on top in stacking order. we will
     * do all read/write ops to the backbuffer, but do a swapbuffers
     * after read/write operations are complete to provide visual
     * feedback. we may want, at some point, to add a parm that enables
     * a swapbuffers.
     */
    int    sw, sh;
    int    dw, dh;
    float  xzoom, yzoom;
    GLenum srcglformat, srcptype, dstptype, dstglformat;
    RMenum src_format, dst_format;

    sw = private_rmImageGetWidth(src);
    sh = private_rmImageGetHeight(src);

    dw = private_rmImageGetWidth(dst);
    dh = private_rmImageGetHeight(dst);

    src_format = private_rmImageGetFormat(src);
    dst_format = private_rmImageGetFormat(dst);

    if (src_format != dst_format)
	return(RM_WHACKED);

    srcglformat = private_rmImageGetOGLFormat(src);
    dstglformat = private_rmImageGetOGLFormat(dst);
    
    srcptype = private_rmImageGetOGLType(src);
    dstptype = private_rmImageGetOGLType(dst);
    
    if (hardware_or_software == RM_HARDWARE)
    {
        private_rmInitInternalImagingPipeline(hwPipe);
	
	glDrawBuffer(GL_BACK); 

	xzoom = (float)(dw + 1) / (float)(sw);
	yzoom = (float)(dh + 1) / (float)(sh);

	glRasterPos2f(0.0F, 0.0F); 
	glPixelZoom((GLfloat)xzoom, (GLfloat)yzoom);
	private_glDrawPixels(sw, sh, srcglformat, srcptype, (unsigned char *)private_rmImageGetPixelData(src), src);

	glFlush();
	glFinish();

	glRasterPos2f(0.0F, 0.0F);
	glPixelZoom((GLfloat)1.0F, (GLfloat)1.0F);

	glReadBuffer(GL_BACK);
	
	glPixelZoom((GLfloat)1.0F, (GLfloat)1.0F);

	private_glReadPixels(0, 0, dw, dh, dstglformat, dstptype, (GLvoid *)private_rmImageGetPixelData(dst));

	private_postRenderSwapBuffersFunc (hwPipe);

#if 0
	/* temp hack: shows the pixels we just read on-screen for a sanity check */
	glReadPixels(0, 0, dw, dh, dstglformat, dstptype, (GLvoid *)private_rmImageGetPixelData(dst));
	glRasterPos2f(0.0, 0.0);
	glClear(GL_COLOR_BUFFER_BIT);
	glDrawPixels(dw, dh, dstglformat, dstptype, (GLvoid *)private_rmImageGetPixelData(dst));
	glDrawBuffer(GL_BACK);
#endif
    }
    else /* RM_SOFTWARE */
    {
	float *t;		/* tmp */
	int i;			/* tmp */
	fake_gluScaleImage(srcglformat, sw, sh, srcptype, private_rmImageGetPixelData(src), dw, dh, dstptype, private_rmImageGetPixelData(dst));

	t = (float *)private_rmImageGetPixelData(src);
	t = (float *)private_rmImageGetPixelData(dst);
	i = 0;
    }

    return(RM_CHILL);

    /*
     * we need code that will accomodate framebuffers which don't
     * support alpha directly when the input image format has alpha.
     * the approach will be to strip out the alpha from the input,
     * treat it as luminance, do a glDrawPixels, then read it back in.
     */
#if 0
	    glPixelTransferf(GL_RED_SCALE, (GLfloat)0.3 * scale);
	    glPixelTransferf(GL_RED_BIAS, (GLfloat)bias);
	    glPixelTransferf(GL_GREEN_SCALE, (GLfloat)0.59 * scale);
	    glPixelTransferf(GL_GREEN_BIAS, (GLfloat)bias);
	    glPixelTransferf(GL_BLUE_SCALE, (GLfloat)0.1 * scale);
	    glPixelTransferf(GL_BLUE_BIAS, (GLfloat)bias);
#endif
}


/* PRIVATE */
RMenum
private_rmImageSetGLScaleAndBias (const RMimage *src)
{
    GLfloat scale, bias;

    scale = src->scale;
    bias = src->bias;

    /* is the image a depth image? if so, apply scale and bias only to depth, otherwise apply to RGBA channels */
    if (private_rmImageGetFormat(src) == RM_IMAGE_DEPTH)
    {
	glPixelTransferf(GL_DEPTH_BIAS, bias);
	glPixelTransferf(GL_DEPTH_SCALE, scale);
    }
    else
    {
        glPixelTransferf(GL_RED_BIAS, bias);
	glPixelTransferf(GL_GREEN_BIAS, bias);
	glPixelTransferf(GL_BLUE_BIAS, bias);
	glPixelTransferf(GL_ALPHA_BIAS, bias);

	glPixelTransferf(GL_RED_SCALE, scale);
	glPixelTransferf(GL_GREEN_SCALE, scale);
	glPixelTransferf(GL_BLUE_SCALE, scale);
	glPixelTransferf(GL_ALPHA_SCALE, scale);
    }

    return(RM_CHILL);
}


/* PRIVATE */
RMenum
private_rmSetLuminancePixelScale(void)
{
    float bias = 0.0;
    float rscale = 0.3, gscale = 0.6, bscale = 0.1, ascale = 0.0;
    
    glPixelTransferf(GL_RED_BIAS, bias);
    glPixelTransferf(GL_GREEN_BIAS, bias);
    glPixelTransferf(GL_BLUE_BIAS, bias);
    glPixelTransferf(GL_ALPHA_BIAS, bias);

    glPixelTransferf(GL_RED_SCALE, rscale);
    glPixelTransferf(GL_GREEN_SCALE, gscale);
    glPixelTransferf(GL_BLUE_SCALE, bscale);
    glPixelTransferf(GL_ALPHA_SCALE, ascale);

    return(RM_CHILL);
}


/* PRIVATE */
RMenum
private_rmUnsetLuminancePixelScale(void)
{
    float bias = 0.0;
    float rscale = 1.0, gscale = 1.0, bscale = 1.0, ascale = 1.0;
    
    glPixelTransferf(GL_RED_BIAS, bias);
    glPixelTransferf(GL_GREEN_BIAS, bias);
    glPixelTransferf(GL_BLUE_BIAS, bias);
    glPixelTransferf(GL_ALPHA_BIAS, bias);

    glPixelTransferf(GL_RED_SCALE, rscale);
    glPixelTransferf(GL_GREEN_SCALE, gscale);
    glPixelTransferf(GL_BLUE_SCALE, bscale);
    glPixelTransferf(GL_ALPHA_SCALE, ascale);

    return(RM_CHILL);
}


/* PRIVATE */
RMenum
private_rmImageUnsetGLScaleAndBias(const RMimage *src)
{
    if (private_rmImageGetFormat(src) == RM_IMAGE_DEPTH)
    {
	glPixelTransferf(GL_DEPTH_BIAS, 0.0F);
	glPixelTransferf(GL_DEPTH_SCALE, 1.0F);
    }
    else
    {
	glPixelTransferf(GL_RED_SCALE, 1.F);
	glPixelTransferf(GL_GREEN_SCALE, 1.F);
	glPixelTransferf(GL_BLUE_SCALE, 1.F);
	glPixelTransferf(GL_ALPHA_SCALE, 1.F);
    
	glPixelTransferf(GL_RED_BIAS, 0.F);
	glPixelTransferf(GL_GREEN_BIAS, 0.F);
	glPixelTransferf(GL_BLUE_BIAS, 0.F);
	glPixelTransferf(GL_ALPHA_BIAS, 0.F);
    }

    return(RM_CHILL);
}


/* PRIVATE */
RMenum
private_glReadPixels (int x,
		      int y,
		      int width,
		      int height,
		      GLenum format,
		      GLenum type,
		      GLvoid *pixels)
{
    if (format == GL_LUMINANCE)
	private_rmSetLuminancePixelScale();
	
    glReadPixels(x, y, width, height, format, type, pixels);
    
    if (format == GL_LUMINANCE)
	private_rmUnsetLuminancePixelScale();

    return(RM_CHILL);
}


/* PRIVATE */
RMenum
private_glDrawPixels (GLsizei width,
		      GLsizei height,
		      GLenum format,
		      GLenum type,
		      const GLvoid *pixeldata,
		      const RMimage *src)
{
    int doScaleBias = 0;
    int doColormap = 0;
    
    if (src != NULL)
    {
	/* set scale & bias */
	if ((src->bias != 0.F) || (src->scale != 1.0F))
	{
	    doScaleBias = 1;
	    private_rmImageSetGLScaleAndBias(src);
	}

	/* set pixel transfer */
	if (src->vismap != NULL)
	{
	    doColormap = 1;
	    private_rmSetPixelTransferMode(src->vismap);
	}
    }
#if 0
    {
	GLboolean b;
	GLfloat   f[4];

	glGetBooleanv(GL_CURRENT_RASTER_POSITION_VALID, &b);
	fprintf(stderr," current raster position is %s valid \n", (b == GL_TRUE) ? "definitely" : "not");

	glGetFloatv(GL_CURRENT_RASTER_POSITION, f);

	if (b == GL_TRUE)
	    fprintf(stderr, " rasterpos: %g %g %g \n", f[0], f[1], f[2]);
    }
#endif
    /* draw pixels */
    glDrawPixels(width, height, format, type, pixeldata);

    /* unset pixel transfer */
    if (doScaleBias == 1)
	private_rmImageUnsetGLScaleAndBias(src);

    /* unset scale & bias */
    if (doColormap == 1)
	private_rmUnsetPixelTransferMode();

    return(RM_CHILL);
}
/* EOF */
