/* $Id: mipmap.c,v 1.2 2000/11/02 00:28:54 mholst Exp $ */

/*
 * Mesa 3-D graphics library
 * Version:  2.0
 * Copyright (C) 1995-1996  Brian Paul
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include "gluP.h"



/*
 * Compute ceiling of integer quotient of A divided by B:
 */
#define CEILING( A, B )  ( (A) % (B) == 0 ? (A)/(B) : (A)/(B)+1 )



#define EPSILON 0.001




GLint gluScaleImage( GLenum format,
                     GLint widthin, GLint heightin,
                     GLenum typein, const void *datain,
                     GLint widthout, GLint heightout,
                     GLenum typeout, void *dataout )
{
   GLuint components, i, j, k;
   GLfloat *tempin, *tempout;
   GLfloat sx, sy;
   GLint unpackrowlength, unpackalignment, unpackskiprows, unpackskippixels;
   GLint packrowlength, packalignment, packskiprows, packskippixels;
   GLint sizein, sizeout;
   GLint rowstride, rowlen;


   /* Determine number of components per pixel */
   switch (format) {
      case GL_COLOR_INDEX:
      case GL_STENCIL_INDEX:
      case GL_DEPTH_COMPONENT:
      case GL_RED:
      case GL_GREEN:
      case GL_BLUE:
      case GL_ALPHA:
      case GL_LUMINANCE:
         components = 1;
	 break;
      case GL_LUMINANCE_ALPHA:
	 components = 2;
	 break;
      case GL_RGB:
	 components = 3;
	 break;
      case GL_RGBA:
	 components = 4;
	 break;
      default:
	 return GLU_INVALID_ENUM;
   }

   /* Determine bytes per input datum */
   switch (typein) {
      case GL_UNSIGNED_BYTE:	sizein = sizeof(GLubyte);	break;
      case GL_BYTE:		sizein = sizeof(GLbyte);	break;
      case GL_UNSIGNED_SHORT:	sizein = sizeof(GLushort);	break;
      case GL_SHORT:		sizein = sizeof(GLshort);	break;
      case GL_UNSIGNED_INT:	sizein = sizeof(GLuint);	break;
      case GL_INT:		sizein = sizeof(GLint);		break;
      case GL_FLOAT:		sizein = sizeof(GLfloat);	break;
      case GL_BITMAP:
	 /* not implemented yet */
      default:
	 return GL_INVALID_ENUM;
   }

   /* Determine bytes per output datum */
   switch (typeout) {
      case GL_UNSIGNED_BYTE:	sizeout = sizeof(GLubyte);	break;
      case GL_BYTE:		sizeout = sizeof(GLbyte);	break;
      case GL_UNSIGNED_SHORT:	sizeout = sizeof(GLushort);	break;
      case GL_SHORT:		sizeout = sizeof(GLshort);	break;
      case GL_UNSIGNED_INT:	sizeout = sizeof(GLuint);	break;
      case GL_INT:		sizeout = sizeof(GLint);	break;
      case GL_FLOAT:		sizeout = sizeof(GLfloat);	break;
      case GL_BITMAP:
	 /* not implemented yet */
      default:
	 return GL_INVALID_ENUM;
   }

   /* Get glPixelStore state */
   glGetIntegerv( GL_UNPACK_ROW_LENGTH, &unpackrowlength );
   glGetIntegerv( GL_UNPACK_ALIGNMENT, &unpackalignment );
   glGetIntegerv( GL_UNPACK_SKIP_ROWS, &unpackskiprows );
   glGetIntegerv( GL_UNPACK_SKIP_PIXELS, &unpackskippixels );
   glGetIntegerv( GL_PACK_ROW_LENGTH, &packrowlength );
   glGetIntegerv( GL_PACK_ALIGNMENT, &packalignment );
   glGetIntegerv( GL_PACK_SKIP_ROWS, &packskiprows );
   glGetIntegerv( GL_PACK_SKIP_PIXELS, &packskippixels );

   /* Allocate storage for intermediate images */
   tempin = (GLfloat *) malloc( widthin * heightin
			        * components * sizeof(GLfloat) );
   if (!tempin) {
      return GLU_OUT_OF_MEMORY;
   }
   tempout = (GLfloat *) malloc( widthout * heightout
		 	         * components * sizeof(GLfloat) );
   if (!tempout) {
      free( tempin );
      return GLU_OUT_OF_MEMORY;
   }


   /*
    * Unpack the pixel data and convert to floating point
    */

   if (unpackrowlength>0) {
      rowlen = unpackrowlength;
   }
   else {
      rowlen = widthin;
   }
   if (sizein >= unpackalignment) {
      rowstride = components * rowlen;
   }
   else {
      rowstride = unpackalignment/sizein
	        * CEILING( components * rowlen * sizein, unpackalignment );
   }

   switch (typein) {
      case GL_UNSIGNED_BYTE:
	 k = 0;
	 for (i=0;i<heightin;i++) {
	    GLubyte *ubptr = (GLubyte *) datain
	                   + i * rowstride
			   + unpackskiprows * rowstride
			   + unpackskippixels * components;
	    for (j=0;j<widthin*components;j++) {
	       tempin[k++] = (GLfloat) *ubptr++;
	    }
	 }
	 break;
      case GL_BYTE:
	 k = 0;
	 for (i=0;i<heightin;i++) {
	    GLbyte *bptr = (GLbyte *) datain
	                 + i * rowstride
			 + unpackskiprows * rowstride
			 + unpackskippixels * components;
	    for (j=0;j<widthin*components;j++) {
	       tempin[k++] = (GLfloat) *bptr++;
	    }
	 }
	 break;
      case GL_UNSIGNED_SHORT:
	 k = 0;
	 for (i=0;i<heightin;i++) {
	    GLushort *usptr = (GLushort *) datain
	                    + i * rowstride
			    + unpackskiprows * rowstride
			    + unpackskippixels * components;
	    for (j=0;j<widthin*components;j++) {
	       tempin[k++] = (GLfloat) *usptr++;
	    }
	 }
	 break;
      case GL_SHORT:
	 k = 0;
	 for (i=0;i<heightin;i++) {
	    GLshort *sptr = (GLshort *) datain
	                  + i * rowstride
			  + unpackskiprows * rowstride
			  + unpackskippixels * components;
	    for (j=0;j<widthin*components;j++) {
	       tempin[k++] = (GLfloat) *sptr++;
	    }
	 }
	 break;
      case GL_UNSIGNED_INT:
	 k = 0;
	 for (i=0;i<heightin;i++) {
	    GLuint *uiptr = (GLuint *) datain
	                  + i * rowstride
			  + unpackskiprows * rowstride
			  + unpackskippixels * components;
	    for (j=0;j<widthin*components;j++) {
	       tempin[k++] = (GLfloat) *uiptr++;
	    }
	 }
	 break;
      case GL_INT:
	 k = 0;
	 for (i=0;i<heightin;i++) {
	    GLint *iptr = (GLint *) datain
	                + i * rowstride
			+ unpackskiprows * rowstride
			+ unpackskippixels * components;
	    for (j=0;j<widthin*components;j++) {
	       tempin[k++] = (GLfloat) *iptr++;
	    }
	 }
	 break;
      case GL_FLOAT:
	 k = 0;
	 for (i=0;i<heightin;i++) {
	    GLfloat *fptr = (GLfloat *) datain
	                  + i * rowstride
			  + unpackskiprows * rowstride
			  + unpackskippixels * components;
	    for (j=0;j<widthin*components;j++) {
	       tempin[k++] = *fptr++;
	    }
	 }
	 break;
      default:
	 return GLU_INVALID_ENUM;
   }


   /*
    * Scale the image!
    */

   sx = (GLfloat) widthin / (GLfloat) widthout;
   sy = (GLfloat) heightin / (GLfloat) heightout;

/*#define POINT_SAMPLE*/
#ifdef POINT_SAMPLE
   for (i=0;i<heightout;i++) {
      GLint ii = i * sy;
      for (j=0;j<widthout;j++) {
	 GLint jj = j * sx;

	 GLfloat *src = tempin + (ii * widthin + jj) * components;
	 GLfloat *dst = tempout + (i * widthout + j) * components;

	 for (k=0;k<components;k++) {
	    *dst++ = *src++;
	 }
      }
   }
#else
   if (sx<1.0 && sy<1.0) {
      /* magnify both width and height:  use weighted sample of 4 pixels */
      GLint i0, i1, j0, j1;
      GLfloat alpha, beta;
      GLfloat *src00, *src01, *src10, *src11;
      GLfloat s1, s2;
      GLfloat *dst;

      for (i=0;i<heightout;i++) {
	 i0 = i * sy;
	 i1 = (i+1) * sy - EPSILON;
	 alpha = i*sy - i0;
	 for (j=0;j<widthout;j++) {
	    j0 = j * sx;
	    j1 = (j+1) * sx - EPSILON;
	    beta = j*sx - j0;

	    /* compute weighted average of pixels in rect (i0,j0)-(i1,j1) */
	    src00 = tempin + (i0 * widthin + j0) * components;
	    src01 = tempin + (i0 * widthin + j1) * components;
	    src10 = tempin + (i1 * widthin + j0) * components;
	    src11 = tempin + (i1 * widthin + j1) * components;

	    dst = tempout + (i * widthout + j) * components;

	    for (k=0;k<components;k++) {
	       s1 = *src00++ * (1.0-beta) + *src01++ * beta;
	       s2 = *src10++ * (1.0-beta) + *src11++ * beta;
	       *dst++ = s1 * (1.0-alpha) + s2 * alpha;
	    }
	 }
      }
   }
   else {
      /* shrink width and/or height:  use an unweighted box filter */
      GLint i0, i1;
      GLint j0, j1;
      GLint ii, jj;
      GLfloat sum, *dst;

      for (i=0;i<heightout;i++) {
	 i0 = i * sy;
	 i1 = (i+1) * sy - EPSILON;
	 for (j=0;j<widthout;j++) {
	    j0 = j * sx;
	    j1 = (j+1) * sx - EPSILON;

	    dst = tempout + (i * widthout + j) * components;

	    /* compute average of pixels in the rectangle (i0,j0)-(i1,j1) */
	    for (k=0;k<components;k++) {
	       sum = 0.0;
	       for (ii=i0;ii<=i1;ii++) {
		  for (jj=j0;jj<=j1;jj++) {
		     sum += *(tempin + (ii * widthin + jj) * components + k);
		  }
	       }
	       sum /= (j1-j0+1) * (i1-i0+1);
	       *dst++ = sum;
	    }
	 }
      }
   }
#endif


   /*
    * Return output image
    */

   if (packrowlength>0) {
      rowlen = packrowlength;
   }
   else {
      rowlen = widthout;
   }
   if (sizeout >= packalignment) {
      rowstride = components * rowlen;
   }
   else {
      rowstride = packalignment/sizeout
	        * CEILING( components * rowlen * sizeout, packalignment );
   }

   switch (typeout) {
      case GL_UNSIGNED_BYTE:
	 k = 0;
	 for (i=0;i<heightout;i++) {
	    GLubyte *ubptr = (GLubyte *) dataout
	                   + i * rowstride
			   + packskiprows * rowstride
			   + packskippixels * components;
	    for (j=0;j<widthout*components;j++) {
	       *ubptr++ = (GLubyte) tempout[k++];
	    }
	 }
	 break;
      case GL_BYTE:
	 k = 0;
	 for (i=0;i<heightout;i++) {
	    GLbyte *bptr = (GLbyte *) dataout
	                 + i * rowstride
			 + packskiprows * rowstride
			 + packskippixels * components;
	    for (j=0;j<widthout*components;j++) {
	       *bptr++ = (GLbyte) tempout[k++];
	    }
	 }
	 break;
      case GL_UNSIGNED_SHORT:
	 k = 0;
	 for (i=0;i<heightout;i++) {
	    GLushort *usptr = (GLushort *) dataout
	                    + i * rowstride
			    + packskiprows * rowstride
			    + packskippixels * components;
	    for (j=0;j<widthout*components;j++) {
	       *usptr++ = (GLushort) tempout[k++];
	    }
	 }
	 break;
      case GL_SHORT:
	 k = 0;
	 for (i=0;i<heightout;i++) {
	    GLshort *sptr = (GLshort *) dataout
	                  + i * rowstride
			  + packskiprows * rowstride
			  + packskippixels * components;
	    for (j=0;j<widthout*components;j++) {
	       *sptr++ = (GLshort) tempout[k++];
	    }
	 }
	 break;
      case GL_UNSIGNED_INT:
	 k = 0;
	 for (i=0;i<heightout;i++) {
	    GLuint *uiptr = (GLuint *) dataout
	                  + i * rowstride
			  + packskiprows * rowstride
			  + packskippixels * components;
	    for (j=0;j<widthout*components;j++) {
	       *uiptr++ = (GLuint) tempout[k++];
	    }
	 }
	 break;
      case GL_INT:
	 k = 0;
	 for (i=0;i<heightout;i++) {
	    GLint *iptr = (GLint *) dataout
	                + i * rowstride
			+ packskiprows * rowstride
			+ packskippixels * components;
	    for (j=0;j<widthout*components;j++) {
	       *iptr++ = (GLint) tempout[k++];
	    }
	 }
	 break;
      case GL_FLOAT:
	 k = 0;
	 for (i=0;i<heightout;i++) {
	    GLfloat *fptr = (GLfloat *) dataout
	                  + i * rowstride
			  + packskiprows * rowstride
			  + packskippixels * components;
	    for (j=0;j<widthout*components;j++) {
	       *fptr++ = tempout[k++];
	    }
	 }
	 break;
      default:
	 return GLU_INVALID_ENUM;
   }


   /* free temporary image storage */
   free( tempin );
   free( tempout );

   return 0;
}



/*
 * Return the largest k such that 2^k <= n.
 */
static GLint ilog2( GLint n )
{
   GLint k;

   if (n<=0) return 0;
   for (k=0; n>>=1; k++) ;
   return k;
}



/*
 * Find the value nearest to n which is also a power of two.
 */
static GLint round2( GLint n )
{
   GLint m;

   for (m=1; m<n; m*=2)
     ;

   /* m>=n */
   if (m-n <= n-m/2) {
      return m;
   }
   else {
      return m/2;
   }
}


/*
 * Given an pixel format and datatype, return the number of bytes to
 * store one pixel.
 */
static GLint bytes_per_pixel( GLenum format, GLenum type )
{
   GLint n, m;

   switch (format) {
      case GL_COLOR_INDEX:
      case GL_STENCIL_INDEX:
      case GL_DEPTH_COMPONENT:
      case GL_RED:
      case GL_GREEN:
      case GL_BLUE:
      case GL_ALPHA:
      case GL_LUMINANCE:
	 n = 1;
	 break;
      case GL_LUMINANCE_ALPHA:
	 n = 2;
	 break;
      case GL_RGB:
	 n = 3;
	 break;
      case GL_RGBA:
	 n = 4;
	 break;
      default:
	 n = 0;
   }

   switch (type) {
      case GL_UNSIGNED_BYTE:	m = sizeof(GLubyte);	break;
      case GL_BYTE:		m = sizeof(GLbyte);	break;
      case GL_BITMAP:		m = 1;			break;
      case GL_UNSIGNED_SHORT:	m = sizeof(GLushort);	break;
      case GL_SHORT:		m = sizeof(GLshort);	break;
      case GL_UNSIGNED_INT:	m = sizeof(GLuint);	break;
      case GL_INT:		m = sizeof(GLint);	break;
      case GL_FLOAT:		m = sizeof(GLfloat);	break;
      default:			m = 0;
   }

   return n * m;
}



/*
 * WARNING: This function isn't finished and has never been tested!!!!
 */
GLint gluBuild1DMipmaps( GLenum target, GLint components,
                         GLint width, GLenum format,
                         GLenum type, const void *data )
{
   GLubyte *texture;
   GLint levels, max_levels;
   GLint new_width, max_width;
   GLint i, j, k, l;

   glGetIntegerv( GL_MAX_TEXTURE_SIZE, &max_width );
   max_levels = ilog2( max_width ) + 1;

   /* Compute how many mipmap images to make */
   levels = ilog2( width ) + 1;
   if (levels>max_levels) {
      levels = max_levels;
   }

   new_width = 1 << (levels-1);

   texture = (GLubyte *) malloc( new_width * components );
   if (!texture) {
      return GLU_OUT_OF_MEMORY;
   }

   if (width != new_width) {
      /* initial rescaling */
      switch (type) {
	 case GL_UNSIGNED_BYTE:
	    {
	       GLubyte *ub_data = (GLubyte *) data;
	       for (i=0;i<new_width;i++) {
		  j = i * width / new_width;
		  for (k=0;k<components;k++) {
		     texture[i*components+k] = ub_data[j*components+k];
		  }
	       }
	    }
	    break;
	 default:
	    /* Not implemented */
	    return GLU_ERROR;
      }
   }

   /* generate and load mipmap images */
   for (l=0;l<levels;l++) {
      glTexImage1D( GL_TEXTURE_1D, l, components, new_width, 0,
		    format, GL_UNSIGNED_BYTE, texture );

      /* Scale image down to 1/2 size */
      new_width = new_width / 2;
      for (i=0;i<new_width;i++) {
	 for (k=0;k<components;k++) {
	    GLint sample1, sample2;
	    sample1 = (GLint) texture[i*2*components+k];
	    sample2 = (GLint) texture[(i*2+1)*components+k];
	    texture[i*components+k] = (GLubyte) ((sample1 + sample2) / 2);
	 }
      }
   }

   free( texture );

   /* make sure remaining mipmap levels are removed */
   for (l=levels;l<max_levels;l++) {
      glTexImage1D( GL_TEXTURE_1D, l, components, 0, 0,
		    format, GL_UNSIGNED_BYTE, NULL );
   }

   return 0;
}



GLint gluBuild2DMipmaps( GLenum target, GLint components,
                         GLint width, GLint height, GLenum format,
                         GLenum type, const void *data )
{
   GLint w, h, maxsize;
   void *image, *newimage;
   GLint neww, newh, level, bpp;
   int error;

   glGetIntegerv( GL_MAX_TEXTURE_SIZE, &maxsize );

   w = round2( width );
   if (w>maxsize) {
      w = maxsize;
   }
   h = round2( height );
   if (h>maxsize) {
      h = maxsize;
   }

   bpp = bytes_per_pixel( format, type );
   if (bpp==0) {
      /* probably a bad format or type enum */
      return GLU_INVALID_ENUM;
   }

   if (w!=width || h!=height) {
      /* must rescale image to get "top" mipmap texture image */
      image = malloc( (w+4) * h * bpp );
      if (!image) {
	 return GLU_OUT_OF_MEMORY;
      }
      error = gluScaleImage( format, width, height, type, data,
			     w, h, type, image );
      if (error) {
	 return error;
      }
   }
   else {
      image = (void *) data;
   }

   level = 0;
   while (1) {
      glTexImage2D( target, level, components, w, h, 0, format, type, image );

      if (w==1 && h==1)  break;

      neww = (w<2) ? 1 : w/2;
      newh = (h<2) ? 1 : h/2;
      newimage = malloc( (neww+4) * newh * bpp );
      if (!newimage) {
	 return GLU_OUT_OF_MEMORY;
      }

      error =  gluScaleImage( format, w, h, type, image,
			      neww, newh, type, newimage );
      if (error) {
	 return error;
      }

      if (image!=data) {
	 free( image );
      }
      image = newimage;

      w = neww;
      h = newh;
      level++;
   }

   if (image!=data) {
      free( image );
   }

   return 0;
}

