/*
 * 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: rmbitmap.c,v 1.5 2005/02/19 16:22:50 wes Exp $
 * Version: $Name: v180-alpha-02 $
 * $Revision: 1.5 $
 * $Log: rmbitmap.c,v $
 * Revision 1.5  2005/02/19 16:22:50  wes
 * Distro sync and consolidation.
 *
 * Revision 1.4  2005/01/23 17:00:22  wes
 * Copyright updated to 2005.
 *
 * Revision 1.3  2004/01/16 16:43:24  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.5  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.4  2002/04/30 19:28:55  wes
 * Updated copyright dates.
 *
 * Revision 1.3  2001/03/31 17:12:38  wes
 * v1.4.0-alpha-2 checkin.
 *
 * 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:47  wes
 * Initial entry - pre-RM120 release, source base for OpenRM 1.2.
 *
 */

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

/*
 * ----------------------------------------------------
 * @Name rmBitmapNew
 @pstart
 RMbitmap * rmBitmapNew (int width,
	                 int height)
 @pend

 @astart
 int width, height - integer values that specify the width and height
    of the bitmap (input).
 @aend

 @dstart
 
 Creates a new RMbitmap object. The size of the bitmap will be
 (width,height), and remains fixed for the lifetime of the object. The
 bitmap data is initialized by this routine to all zeros.

 Returns a handle to the new object upon success, or NULL upon
 failure.
 
 @dend
 * ----------------------------------------------------
 */
RMbitmap *
rmBitmapNew (int width,
	     int height)
{
    int       bitmap_width_in_bytes;
    RMbitmap *b;
    
    /* given width in pixels, compute width in bytes (windows wants an even multiple of four bytes */
    bitmap_width_in_bytes = (width / 8);

    /* if not an exact even multiple of 8 bits, incr byte count */
    if ((width & 0x7) != 0)
	bitmap_width_in_bytes++;

#ifdef RM_WIN
    /*
     * MS OpenGL is buggy - it assumes that all pixel data is aligned
     * to 4-byte boundaries, regardless of the values specified by
     * glPixelStorei(). this code takes care of that for you by rounding
     * the internal number of bytes per scanline up to the nearest
     * 4-byte boundary.
     */
    {
        while ((bitmap_width_in_bytes & 0x3) != 0)
	    bitmap_width_in_bytes++;
    }
#endif

    b = (RMbitmap *)malloc(sizeof(RMbitmap));
    memset(b, 0, sizeof(RMbitmap));

    private_rmBitmapSetWidth(b, width);
    private_rmBitmapSetHeight(b, height);
    private_rmBitmapSetBytesPerScanline(b, bitmap_width_in_bytes);
    private_rmBitmapSetPBSize(b, bitmap_width_in_bytes * height);

    b->pixeldata = (unsigned char *)malloc(sizeof(unsigned char) * private_rmBitmapGetPBSize(b));
    if (RM_ASSERT(private_rmBitmapGetPixelData(b), "rmBitmapNew() error: unable to allocate memory for the bitmap data.") == RM_WHACKED)
    {
	free((void *)b);
	return(NULL);
    }
    memset(b->pixeldata, 0, private_rmBitmapGetPBSize(b));
    
    return(b);
}


/*
 * ----------------------------------------------------
 * @Name rmBitmapCopy
 @pstart
 RMenum rmBitmapCopy (RMbitmap *dst,
	              const RMbitmap *src)
 @pend

 @astart
 RMbitmap *dst - the destination RMbitmap object (output).
 
 const RMbitmap *src - the source RMbitmap object (input).
 @aend

 @dstart

 Copies the bitmap data from one RMbitmap object to another. Both
 RMbitmaps must be fully configured (created with rmBitmapNew), and
 must be the same size. Size information may be obtained with
 rmBitmapGetSize, but is set at the time the RMbitmap is created with
 rmBitmapNew.

 Returns RM_CHILL to the caller upon success, or RM_WHACKED upon
 failure.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmBitmapCopy (RMbitmap *dst,
	      const RMbitmap *src)
{
    /* the only check is for whether or not the sizes are the same */
    if (RM_ASSERT(src, "rmBitmapCopy() source bitmap is NULL") == RM_WHACKED)
	return(RM_WHACKED);
    if (RM_ASSERT(dst, "rmBitmapCopy() dest bitmap is NULL") == RM_WHACKED)
	return(RM_WHACKED);

    if ((private_rmBitmapGetWidth(src) != private_rmBitmapGetWidth(dst)) || (private_rmBitmapGetHeight(src) != private_rmBitmapGetHeight(dst)))
    {
        rmError("rmBitmapCopy() error: the sizes of the source and dest. bitmaps are not the same: no copy will occur. ");
	return(RM_WHACKED);
    }
    else
    {
	memcpy((void *)(private_rmBitmapGetPixelData(dst)), (void *)(private_rmBitmapGetPixelData(src)), private_rmBitmapGetPBSize(src));
    }
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmBitmapDup
 @pstart
 RMbitmap *rmBitmapDup (const RMbitmap *src)
 @pend

 @astart
 const RMbitmap *src - a handle to a source RMbitmap object.
 @aend

 @dstart
 
 Creates a new RMbitmap object that is an exact replica of an existing
 one. Upon return from this routine, the new RMbitmap object is an
 exact duplicate, except that both source and destination RMbitmap
 objects have their own buffers. A change to one will not affect the
 other.

 Returns a handle to a new RMbitmap object upon success, or NULL upon
 failure.
 
 @dend
 * ----------------------------------------------------
 */
RMbitmap *
rmBitmapDup (const RMbitmap *src)
{
    int       w, h, bytes_per_scanline;
    RMbitmap *dst;

    if (RM_ASSERT(src, "rmBitmapDup() null source bitmap pointer") == RM_WHACKED)
	return(NULL);

    rmBitmapGetSize(src, &w, &h, &bytes_per_scanline);
    
    dst = rmBitmapNew(w, h);
    if (dst == NULL)
	return(NULL);
    
    rmBitmapCopy(dst, src);

    return(dst);
}


/*
 * ----------------------------------------------------
 * @Name rmBitmapDelete
 @pstart
 void rmBitmapDelete (RMbitmap *toDelete)
 @pend

 @astart
 RMbitmap *toDelete - a handle to the RMbitmap object to be deleted.
 @aend

 @dstart

 Releases resources associated with an RMbitmap object. This routine
 is the opposite of rmBitmapNew().
 
 @dend
 * ----------------------------------------------------
 */
void
rmBitmapDelete (RMbitmap *b)
{
    RM_ASSERT(b, "rmBitmapDelete() null input bitmap \n");

    if (b->pixeldata != NULL)
	free((void *)b->pixeldata);
    free((void *)b);
}


/*
 * ----------------------------------------------------
 * @Name rmBitmapSetPixelData
 @pstart
 RMenum rmBitmapSetPixelData (RMbitmap *toModify,
		              const void *pixeldata)

 @pend

 @astart
 RMbitmap *toModify - a handle to the RMbitmap object who's bitmap
     data will be modified by this call (input).

 const void *pixeldata - a handle to the raw bitmap data.
 @aend

 @dstart
 
 Copies bitmap data from the caller's memory into the RMbitmap's
 internal buffer. Unlike other RM objects, RMbitmap objects do not
 allows for shared data management of pixel data.

 By convention, bitmap data must be scanline padded to the nearest
 4-byte boundary. This is an artifact of the Win32 OpenGL
 implementation.

 Returns RM_CHILL to the caller if the operation was successful,
 otherwise RM_WHACKED is returned.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmBitmapSetPixelData (RMbitmap *b,
		      const void *pixeldata)
{
    
    if ((RM_ASSERT(b, "rmBitmapSetPixelData() error: the input bitmap is NULL.") == RM_WHACKED) ||
	(RM_ASSERT(pixeldata, "rmBitmapSetPixelData() error: the input bitmap data is NULL.") == RM_WHACKED))
	return(RM_WHACKED);

    memcpy((void *)(private_rmBitmapGetPixelData(b)), pixeldata, private_rmBitmapGetPBSize(b));
    
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmBitmapGetPixelData
 @pstart
 void * rmBitmapGetPixelData (const RMbitmap *toQuery)
 @pend

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

 @dstart
 
 Returns to the caller a handle to the raw bitmap data from the bitmap
 object upon success, otherwise NULL is returned. The data can be
 interpreted as raw byte data, where the high order bit of the first
 byte corresponds to the upper-left most bit in the image.

 Use rmBitmapSetPixelData() to set this data (in toto), or
 rmBitmapSetBit to turn on a single bit. Use rmBitmapGetSize() to
 query the dimensions of the bitmap.

 @dend
 * ----------------------------------------------------
 */
void *
rmBitmapGetPixelData (const RMbitmap *toQuery)
{
    if (RM_ASSERT(toQuery, "rmBitmapGetPixelData() error: the input bitmap is NULL.") == RM_WHACKED)
	return(NULL);

    return(private_rmBitmapGetPixelData(toQuery));
}


/*
 * ----------------------------------------------------
 * @Name rmBitmapGetSize
 @pstart
 RMenum rmBitmapGetSize (const RMbitmap *toQuery,
		         int *width_return,
			 int *height_return,
			 int *bytes_width_return)

 @pend

 @astart
 const RMbitmap *toQuery - a handle to the RMbitmap object to query.
 
 int *width_return, *height_return - pointers to caller-supplied int's
    that will be set to contain the width and height dimensions of the
    the bitmap.

 int *bytes_width_return - pointer to caller-supplied int. This will
    be set to reflect the real number of bytes per scanline in the
    RMbitmap objects pixel buffer. This value is computed by RM to
    honor local pixel storage restrictions. 
 @aend

 @dstart

 Returns to the caller the width and height of the bitmap of an
 RMbitmap object. Since the number of bytes per scanline of the bitmap
 buffer may not necessarily be equal to width/8, the "real" number of
 bytes per scanline is returned to the caller by this routine.

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.

 @dend
 * ----------------------------------------------------
 */
RMenum
rmBitmapGetSize (const RMbitmap *b,
		 int *width_return,
		 int *height_return,
		 int *bytes_width_return)
{
    if (RM_ASSERT(b, "rmBitmapGetSize() null input RMbitmap pointer. \n") == RM_WHACKED)
	return(RM_WHACKED);

    if (width_return != NULL)
	*width_return = private_rmBitmapGetWidth(b);

    if (height_return != NULL)
	*height_return = private_rmBitmapGetHeight(b);
    
    if (bytes_width_return != NULL)
	*bytes_width_return = private_rmBitmapGetBytesPerScanline(b);
    
    return(RM_CHILL);
}


/*
 * ----------------------------------------------------
 * @Name rmBitmapSetBit
 @pstart
 RMenum rmBitmapSetBit (RMbitmap *toModify,
	                int columnIndex,
		        int rowIndex)
 @pend

 @astart
 RMbitmap *toModify - a handle to the RMbitmap object to modify.

 int columnIndex, rowIndex - integer values specifying the coordinate
     of the bit to set in terms of index location. 
 @aend

 @dstart

 This routine is used to set, or turn on, a single bit in an RMbitmap
 object. There is no corresponding routine to turn a bit off. To turn
 a bit off, you have to write the entire bitmap buffer with
 rmBitmapSetPixelData.

 Returns RM_CHILL upon success, or RM_WHACKED upon failure.
 
 @dend
 * ----------------------------------------------------
 */
RMenum
rmBitmapSetBit (RMbitmap *b,
	        int col,
	        int row)
{
    int            w, h, bytes, indx;
    unsigned char *p;
    unsigned int   mask;

    /* check for within bounds */
    if (RM_ASSERT(b, "rmBitmapSetBit() null input RMbitmap pointer.") == RM_WHACKED)
	return(RM_WHACKED);
    if (RM_ASSERT(private_rmBitmapGetPixelData(b), "rmBitmapSetBit() - some type of internal error has occured - there is no bitmap image data allocated for this RMbitmap object.") == RM_WHACKED)
	return(RM_WHACKED);
    
    rmBitmapGetSize(b, &w, &h, &bytes);
    if ((col < 0) || (col > (w - 1)) || (row < 0) || (row > (h - 1)))
    {
	char buf[128];

	sprintf(buf, "rmBitmapSetBit() error - input row or column parameters out of range. valid range is (%d,%d), you gave me (%d,%d) \n", w, h, col, row);
	rmError(buf);
	return(RM_WHACKED);
    }

    indx = row * private_rmBitmapGetBytesPerScanline(b) +
	(col / 8);
    p = b->pixeldata + indx;
    mask = (1 << (7 - (col % 8)));
    *p |= mask;

#if 0
#define RM_BITMAP_SET_BIT(a, j, i)	(a[j][i / 8] |=  (1 << (7-(i % 8))));
    RM_BITMAP_SET_BIT(b->bitmap_mask, row, col);
#endif
    
    return(RM_CHILL);
}
/* EOF */
