/*
 * SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
 * Copyright (C) 1991-2000 Silicon Graphics, Inc. All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice including the dates of first publication and
 * either this permission notice or a reference to
 * http://oss.sgi.com/projects/FreeB/
 * shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * SILICON GRAPHICS, INC. BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
 * OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * Except as contained in this notice, the name of Silicon Graphics, Inc.
 * shall not be used in advertising or otherwise to promote the sale, use or
 * other dealings in this Software without prior written authorization from
 * Silicon Graphics, Inc.
 *
 * OpenGL ES CM 1.0 port of part of GLU by Mike Gorchak <mike@malva.ua>
 */

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>    /* UINT_MAX */
#include <math.h>

#include "glues_mipmap.h"

typedef union
{
   unsigned char ub[4];
   unsigned short us[2];
   unsigned int ui;
   char b[4];
   short s[2];
   int i;
   float f;
} Type_Widget;

/* Pixel storage modes */
typedef struct
{
   GLint pack_alignment;
   GLint pack_row_length;
   GLint pack_skip_rows;
   GLint pack_skip_pixels;
   GLint pack_swap_bytes;
   GLint pack_image_height;

   GLint unpack_alignment;
   GLint unpack_row_length;
   GLint unpack_skip_rows;
   GLint unpack_skip_pixels;
   GLint unpack_swap_bytes;
   GLint unpack_image_height;
} PixelStorageModes;

static int gluBuild2DMipmapLevelsCore(GLenum, GLint, GLsizei, GLsizei,
                                      GLsizei, GLsizei, GLenum, GLenum,
                                      GLint, GLint, GLint, const void*);

/*
 * internal function declarations
 */
static GLfloat bytes_per_element(GLenum type);
static GLint elements_per_group(GLenum format, GLenum type);
static GLint image_size(GLint width, GLint height, GLenum format, GLenum type);
static void fill_image(const PixelStorageModes*,
                       GLint width, GLint height, GLenum format,
                       GLenum type, GLboolean index_format,
                       const void* userdata, GLushort* newimage);
static void empty_image(const PixelStorageModes*,
                        GLint width, GLint height, GLenum format,
                        GLenum type, GLboolean index_format,
                        const GLushort* oldimage, void* userdata);
static void scale_internal(GLint components, GLint widthin, GLint heightin,
                           const GLushort* datain,
                           GLint widthout, GLint heightout,
                           GLushort* dataout);

static void scale_internal_ubyte(GLint components, GLint widthin,
                           GLint heightin, const GLubyte* datain,
                           GLint widthout, GLint heightout,
                           GLubyte* dataout, GLint element_size,
                           GLint ysize, GLint group_size);

static int checkMipmapArgs(GLenum, GLenum, GLenum);
static GLboolean legalFormat(GLenum);
static GLboolean legalType(GLenum);
static GLboolean isTypePackedPixel(GLenum);
static GLboolean isLegalFormatForPackedPixelType(GLenum, GLenum);
static GLboolean isLegalLevels(GLint, GLint, GLint, GLint);
static void closestFit(GLenum, GLint, GLint, GLint, GLenum, GLenum,
                       GLint*, GLint*);

/* packedpixel type scale routines */
static void extract565(int, const void*, GLfloat []);
static void shove565(const GLfloat [], int ,void*);
static void extract4444(int, const void*, GLfloat []);
static void shove4444(const GLfloat [], int ,void*);
static void extract5551(int, const void*, GLfloat []);
static void shove5551(const GLfloat [], int ,void*);
static void scaleInternalPackedPixel(int,
                                     void (*)(int, const void*,GLfloat []),
                                     void (*)(const GLfloat [],int, void*),
                                     GLint,GLint, const void*,
                                     GLint,GLint,void*,GLint,GLint,GLint);
static void halveImagePackedPixel(int,
                                  void (*)(int, const void*,GLfloat []),
                                  void (*)(const GLfloat [],int, void*),
                                  GLint, GLint, const void*,
                                  void*, GLint, GLint, GLint);
static void halve1DimagePackedPixel(int,
                                    void (*)(int, const void*,GLfloat []),
                                    void (*)(const GLfloat [],int, void*),
                                    GLint, GLint, const void*,
                                    void*, GLint, GLint, GLint);

static void halve1Dimage_ubyte(GLint, GLuint, GLuint,const GLubyte*,
                               GLubyte*, GLint, GLint, GLint);

static void retrieveStoreModes(PixelStorageModes* psm)
{
   psm->unpack_alignment=1;
   psm->unpack_row_length=0;
   psm->unpack_skip_rows=0;
   psm->unpack_skip_pixels=0;
   psm->unpack_swap_bytes=0;

   psm->pack_alignment=1;
   psm->pack_row_length=0;
   psm->pack_skip_rows=0;
   psm->pack_skip_pixels=0;
   psm->pack_swap_bytes=0;
}

static int computeLog(GLuint value)
{
   int i=0;

   /* Error! */
   if (value==0)
   {
      return -1;
   }

   for (;;)
   {
      if (value & 1)
      {
         /* Error ! */
         if (value!=1)
         {
            return -1;
         }
         return i;
      }
      value =value >> 1;
      i++;
   }
}

/*
** Compute the nearest power of 2 number.  This algorithm is a little
** strange, but it works quite well.
*/
static int nearestPower(GLuint value)
{
   int i=1;

   /* Error! */
   if (value==0)
   {
      return -1;
   }

   for (;;)
   {
      if (value==1)
      {
         return i;
      }
      else
      {
         if (value==3)
         {
            return i*4;
         }
      }
      value=value>>1;
      i*=2;
   }
}

#define __GLU_SWAP_2_BYTES(s)\
(GLushort)(((GLushort)((const GLubyte*)(s))[1])<<8 | ((const GLubyte*)(s))[0])

#define __GLU_SWAP_4_BYTES(s)\
(GLuint)(((GLuint)((const GLubyte*)(s))[3])<<24 | \
        ((GLuint)((const GLubyte*)(s))[2])<<16 | \
        ((GLuint)((const GLubyte*)(s))[1])<<8  | ((const GLubyte*)(s))[0])

static void halveImage(GLint components, GLuint width, GLuint height,
                       const GLushort* datain, GLushort* dataout)
{
   int i, j, k;
   int newwidth, newheight;
   int delta;
   GLushort* s;
   const GLushort* t;

   newwidth=width/2;
   newheight=height/2;
   delta=width*components;
   s=dataout;
   t=datain;

   /* Piece o' cake! */
   for (i=0; i<newheight; i++)
   {
      for (j=0; j<newwidth; j++)
      {
         for (k=0; k<components; k++)
         {
            s[0]=(t[0]+t[components]+t[delta]+t[delta+components]+2)/4;
            s++; t++;
         }
         t+=components;
      }
      t+=delta;
    }
}

static void halveImage_ubyte(GLint components, GLuint width, GLuint height,
                        const GLubyte* datain, GLubyte* dataout,
                        GLint element_size, GLint ysize, GLint group_size)
{
   int i, j, k;
   int newwidth, newheight;
   int padBytes;
   GLubyte* s;
   const char* t;

   /* handle case where there is only 1 column/row */
   if (width==1 || height==1)
   {
      assert(!(width==1 && height==1) ); /* can't be 1x1 */
      halve1Dimage_ubyte(components, width, height, datain, dataout, element_size, ysize, group_size);
      return;
   }

   newwidth=width/2;
   newheight=height/2;
   padBytes=ysize-(width*group_size);
   s=dataout;
   t=(const char*)datain;

   /* Piece o' cake! */
   for (i=0; i<newheight; i++)
   {
      for (j=0; j<newwidth; j++)
      {
         for (k=0; k<components; k++)
         {
            s[0]=(*(const GLubyte*)t+
                  *(const GLubyte*)(t+group_size)+
                  *(const GLubyte*)(t+ysize)+
                  *(const GLubyte*)(t+ysize+group_size)+2)/4;
                  s++; t+=element_size;
         }
         t+=group_size;
      }
      t+=padBytes;
      t+=ysize;
   }
}

/* */
static void halve1Dimage_ubyte(GLint components, GLuint width, GLuint height,
                               const GLubyte* dataIn, GLubyte* dataOut,
                               GLint element_size, GLint ysize,
                               GLint group_size)
{
   GLint halfWidth=width/2;
   GLint halfHeight=height/2;
   const char* src=(const char*)dataIn;
   GLubyte* dest=dataOut;
   int jj;

   assert(width==1 || height==1); /* must be 1D */
   assert(width!=height);         /* can't be square */

   if (height==1)
   {                              /* 1 row */
      assert(width!=1);           /* widthxheight can't be 1x1 */
      halfHeight=1;

      for (jj=0; jj<halfWidth; jj++)
      {
         int kk;
         for (kk=0; kk<components; kk++)
         {
            *dest=(*(const GLubyte*)src+
                   *(const GLubyte*)(src+group_size))/2;

            src+=element_size;
            dest++;
         }
         src+=group_size;         /* skip to next 2 */
      }
      {
         int padBytes=ysize-(width*group_size);
         src+=padBytes;           /* for assertion only */
      }
   }
   else
   {
      if (width==1)
      {                           /* 1 column */
         int padBytes=ysize-(width*group_size);
         assert(height!=1);       /* widthxheight can't be 1x1 */
         halfWidth=1;

         /* one vertical column with possible pad bytes per row */
         /* average two at a time */
         for (jj=0; jj<halfHeight; jj++)
         {
            int kk;

            for (kk=0; kk<components; kk++)
            {
               *dest=(*(const GLubyte*)src+*(const GLubyte*)(src+ysize))/2;

               src+=element_size;
               dest++;
            }
            src+=padBytes; /* add pad bytes, if any, to get to end to row */
            src+=ysize;
         }
      }
   }

   assert(src==&((const char*)dataIn)[ysize*height]);
   assert((char*)dest==&((char*)dataOut)[components*element_size*halfWidth*halfHeight]);
} /* halve1Dimage_ubyte() */

static void scale_internal(GLint components, GLint widthin, GLint heightin,
                           const GLushort* datain,
                           GLint widthout, GLint heightout,
                           GLushort* dataout)
{
   float x, lowx, highx, convx, halfconvx;
   float y, lowy, highy, convy, halfconvy;
   float xpercent,ypercent;
   float percent;

   /* Max components in a format is 4, so... */
   float totals[4];
   float area;
   int i,j,k,yint,xint,xindex,yindex;
   int temp;

   if (widthin==widthout*2 && heightin==heightout*2)
   {
      halveImage(components, widthin, heightin, datain, dataout);
      return;
   }

   convy=(float)heightin/heightout;
   convx=(float)widthin/widthout;
   halfconvx=convx/2;
   halfconvy=convy/2;
   for (i=0; i<heightout; i++)
   {
      y=convy*(i+0.5f);
      if (heightin>heightout)
      {
         highy=y+halfconvy;
         lowy=y-halfconvy;
      }
      else
      {
         highy=y+0.5f;
         lowy=y-0.5f;
      }
      for (j=0; j<widthout; j++)
      {
         x=convx*(j+0.5f);
         if (widthin>widthout)
         {
            highx=x+halfconvx;
            lowx=x-halfconvx;
         }
         else
         {
            highx=x+0.5f;
            lowx=x-0.5f;
         }
         /*
         ** Ok, now apply box filter to box that goes from (lowx, lowy)
         ** to (highx, highy) on input data into this pixel on output
         ** data.
         */
         totals[0] = totals[1] = totals[2] = totals[3] = 0.0;
         area = 0.0;

         y=lowy;
         yint=(int)floor(y);
         while (y<highy)
         {
            yindex=(yint+heightin) % heightin;
            if (highy<yint+1)
            {
               ypercent=highy-y;
            }
            else
            {
               ypercent=yint+1-y;
            }

            x=lowx;
            xint=(int)floor(x);

            while (x<highx)
            {
               xindex=(xint+widthin) % widthin;
               if (highx<xint+1)
               {
                  xpercent=highx-x;
               }
               else
               {
                  xpercent=xint+1-x;
               }

               percent=xpercent*ypercent;
               area+=percent;
               temp=(xindex+(yindex*widthin))*components;
               for (k=0; k<components; k++)
               {
                  totals[k]+=datain[temp+k]*percent;
               }

               xint++;
               x=(GLfloat)xint;
            }
            yint++;
            y=(GLfloat)yint;
         }

         temp=(j+(i*widthout))*components;
         for (k=0; k<components; k++)
         {
            /* totals[] should be rounded in the case of enlarging an RGB
             * ramp when the type is 4444
             */
            dataout[temp+k]=(GLushort)((totals[k]+0.5f)/area);
         }
      }
   }
}

static void scale_internal_ubyte(GLint components, GLint widthin,
                                 GLint heightin, const GLubyte* datain,
                                 GLint widthout, GLint heightout,
                                 GLubyte* dataout, GLint element_size,
                                 GLint ysize, GLint group_size)
{
   float convx;
   float convy;
   float percent;

   /* Max components in a format is 4, so... */
   float totals[4];
   float area;
   int i, j, k, xindex;

   const char* temp;
   const char* temp0;
   const char* temp_index;
   int outindex;

   int lowx_int, highx_int, lowy_int, highy_int;
   float x_percent, y_percent;
   float lowx_float, highx_float, lowy_float, highy_float;
   float convy_float, convx_float;
   int convy_int, convx_int;
   int l, m;
   const char* left;
   const char* right;

   if (widthin==widthout*2 && heightin==heightout*2)
   {
      halveImage_ubyte(components, widthin, heightin, (const GLubyte*)datain,
                      (GLubyte*)dataout, element_size, ysize, group_size);
      return;
   }

   convy=(float)heightin/heightout;
   convx=(float)widthin/widthout;
   convy_int=(int)floor(convy);
   convy_float=convy-convy_int;
   convx_int=(int)floor(convx);
   convx_float=convx-convx_int;

   area=convx*convy;

   lowy_int=0;
   lowy_float=0;
   highy_int=convy_int;
   highy_float=convy_float;

   for (i=0; i<heightout; i++)
   {
      /* Clamp here to be sure we don't read beyond input buffer. */
      if (highy_int>=heightin)
      {
         highy_int=heightin-1;
      }
      lowx_int = 0;
      lowx_float = 0;
      highx_int = convx_int;
      highx_float = convx_float;

      for (j=0; j<widthout; j++)
      {
         /*
         ** Ok, now apply box filter to box that goes from (lowx, lowy)
         ** to (highx, highy) on input data into this pixel on output
         ** data.
         */
         totals[0] = totals[1] = totals[2] = totals[3] = 0.0;

         /* calculate the value for pixels in the 1st row */
         xindex=lowx_int*group_size;
         if ((highy_int>lowy_int) && (highx_int>lowx_int))
         {
            y_percent=1-lowy_float;
            temp=(const char*)datain+xindex+lowy_int*ysize;
            percent=y_percent*(1-lowx_float);
            for (k=0, temp_index=temp; k<components; k++, temp_index+=element_size)
            {
               totals[k]+=(GLubyte)(*(temp_index))*percent;
            }

            left=temp;
            for(l=lowx_int+1; l<highx_int; l++)
            {
               temp+=group_size;
               for (k=0, temp_index=temp; k<components; k++, temp_index+=element_size)
               {
                  totals[k]+=(GLubyte)(*(temp_index))*y_percent;
               }
            }

            temp+=group_size;
            right=temp;
            percent=y_percent*highx_float;
            for (k=0, temp_index=temp; k<components; k++, temp_index+=element_size)
            {
               totals[k]+=(GLubyte)(*(temp_index))*percent;
            }

            /* calculate the value for pixels in the last row */
            y_percent=highy_float;
            percent=y_percent*(1-lowx_float);
            temp=(const char*)datain+xindex+highy_int*ysize;
            for (k=0, temp_index=temp; k<components; k++, temp_index+=element_size)
            {
               totals[k]+=(GLubyte)(*(temp_index))*percent;
            }
            for (l=lowx_int+1; l<highx_int; l++)
            {
               temp+=group_size;
               for (k=0, temp_index=temp; k<components; k++, temp_index+=element_size)
               {
                  totals[k]+=(GLubyte)(*(temp_index))*y_percent;
               }
            }
            temp+=group_size;
            percent=y_percent*highx_float;
            for (k=0, temp_index=temp; k<components; k++, temp_index+=element_size)
            {
               totals[k]+=(GLubyte)(*(temp_index))*percent;
            }

            /* calculate the value for pixels in the 1st and last column */
            for (m=lowy_int+1; m<highy_int; m++)
            {
               left+=ysize;
               right+=ysize;
               for (k=0; k<components; k++, left+=element_size, right+=element_size)
               {
                  totals[k]+=(GLubyte)(*(left))*(1-lowx_float)+(GLubyte)(*(right))*highx_float;
               }
            }
         }
         else
         {
            if (highy_int>lowy_int)
            {
               x_percent=highx_float-lowx_float;
               percent=(1-lowy_float)*x_percent;
               temp=(const char*)datain+xindex+lowy_int*ysize;
               for (k=0, temp_index=temp; k<components; k++, temp_index+=element_size)
               {
                  totals[k]+=(GLubyte)(*(temp_index))*percent;
               }
               for (m=lowy_int+1; m<highy_int; m++)
               {
                  temp+=ysize;
                  for (k=0, temp_index=temp; k<components; k++, temp_index+=element_size)
                  {
                     totals[k]+=(GLubyte)(*(temp_index))*x_percent;
                  }
               }
               percent = x_percent * highy_float;
               temp+=ysize;
               for (k=0, temp_index=temp; k<components; k++, temp_index+=element_size)
               {
                  totals[k]+=(GLubyte)(*(temp_index))*percent;
               }
            }
            else
            {
               if (highx_int>lowx_int)
               {
                  y_percent=highy_float-lowy_float;
                  percent=(1-lowx_float)*y_percent;
                  temp=(const char*)datain+xindex+lowy_int*ysize;
                  for (k=0, temp_index=temp; k<components; k++, temp_index+=element_size)
                  {
                     totals[k]+=(GLubyte)(*(temp_index))*percent;
                  }
                  for (l=lowx_int+1; l<highx_int; l++)
                  {
                     temp+=group_size;
                     for (k=0, temp_index=temp; k<components; k++, temp_index+=element_size)
                     {
                        totals[k]+=(GLubyte)(*(temp_index))*y_percent;
                     }
                  }
                  temp+=group_size;
                  percent=y_percent*highx_float;
                  for (k=0, temp_index=temp; k<components; k++, temp_index+=element_size)
                  {
                     totals[k] += (GLubyte)(*(temp_index)) * percent;
                  }
               }
               else
               {
                  percent=(highy_float-lowy_float)*(highx_float-lowx_float);
                  temp=(const char*)datain+xindex+lowy_int*ysize;
                  for (k=0, temp_index=temp; k<components; k++, temp_index+=element_size)
                  {
                     totals[k]+=(GLubyte)(*(temp_index))*percent;
                  }
               }
            }
         }

         /* this is for the pixels in the body */
         temp0=(const char*)datain+xindex+group_size+(lowy_int+1)*ysize;
         for (m=lowy_int+1; m<highy_int; m++)
         {
            temp=temp0;
            for(l=lowx_int+1; l<highx_int; l++)
            {
               for (k=0, temp_index=temp; k<components; k++, temp_index+=element_size)
               {
                  totals[k]+=(GLubyte)(*(temp_index));
               }
               temp+=group_size;
            }
            temp0 += ysize;
         }

         outindex=(j+(i*widthout))*components;
         for (k=0; k<components; k++)
         {
            dataout[outindex+k]=(GLubyte)(totals[k]/area);
         }
         lowx_int=highx_int;
         lowx_float=highx_float;
         highx_int += convx_int;
         highx_float += convx_float;
         if (highx_float>1)
         {
            highx_float-=1.0;
            highx_int++;
         }
      }
      lowy_int=highy_int;
      lowy_float=highy_float;
      highy_int+=convy_int;
      highy_float+=convy_float;
      if (highy_float>1)
      {
         highy_float-=1.0;
         highy_int++;
      }
   }
}

static int checkMipmapArgs(GLenum internalFormat, GLenum format, GLenum type)
{
   if (!legalFormat(format) || !legalType(type))
   {
      return GLU_INVALID_ENUM;
   }

   if (!isLegalFormatForPackedPixelType(format, type))
   {
      return GLU_INVALID_OPERATION;
   }

   return 0;
} /* checkMipmapArgs() */

static GLboolean legalFormat(GLenum format)
{
   switch(format)
   {
      case GL_ALPHA:
      case GL_RGB:
      case GL_RGBA:
      case GL_LUMINANCE:
      case GL_LUMINANCE_ALPHA:
           return GL_TRUE;
      default:
           return GL_FALSE;
   }
}

static GLboolean legalType(GLenum type)
{
   switch(type)
   {
      case GL_UNSIGNED_BYTE:
      case GL_UNSIGNED_SHORT_5_6_5:
      case GL_UNSIGNED_SHORT_4_4_4_4:
      case GL_UNSIGNED_SHORT_5_5_5_1:
           return GL_TRUE;
      default:
           return GL_FALSE;
   }
}

/* */
static GLboolean isTypePackedPixel(GLenum type)
{
   assert(legalType(type));

   if (type==GL_UNSIGNED_SHORT_5_6_5 ||
       type==GL_UNSIGNED_SHORT_4_4_4_4 ||
       type==GL_UNSIGNED_SHORT_5_5_5_1)
   {
      return 1;
   }
   else
   {
      return 0;
   }
} /* isTypePackedPixel() */

/* Determines if the packed pixel type is compatible with the format */
static GLboolean isLegalFormatForPackedPixelType(GLenum format, GLenum type)
{
   /* if not a packed pixel type then return true */
   if (!isTypePackedPixel(type))
   {
      return GL_TRUE;
   }

   /* 5_6_5 is only compatible with RGB */
   if ((type==GL_UNSIGNED_SHORT_5_6_5) && format!=GL_RGB)
   {
      return GL_FALSE;
   }

   /* 4_4_4_4 & 5_5_5_1
    * are only compatible with RGBA
    */
   if ((type==GL_UNSIGNED_SHORT_4_4_4_4 || type==GL_UNSIGNED_SHORT_5_5_5_1) &&
       (format != GL_RGBA))
   {
      return GL_FALSE;
   }

   return GL_TRUE;
} /* isLegalFormatForPackedPixelType() */

static GLboolean isLegalLevels(GLint userLevel,GLint baseLevel,GLint maxLevel,
                               GLint totalLevels)
{
   if (baseLevel < 0 || baseLevel < userLevel || maxLevel < baseLevel ||
       totalLevels < maxLevel)
   {
      return GL_FALSE;
   }
   else
   {
      return GL_TRUE;
   }
} /* isLegalLevels() */

/* Given user requested texture size, determine if it fits. If it
 * doesn't then halve both sides and make the determination again
 * until it does fit (for IR only).
 */
static void closestFit(GLenum target, GLint width, GLint height,
                       GLint internalFormat, GLenum format, GLenum type,
                       GLint *newWidth, GLint *newHeight)
{
   GLint maxsize;

   glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxsize);

   /* clamp user's texture sizes to maximum sizes, if necessary */
   *newWidth=nearestPower(width);
   if (*newWidth>maxsize)
   {
      *newWidth=maxsize;
   }
   *newHeight=nearestPower(height);
   if (*newHeight>maxsize)
   {
      *newHeight = maxsize;
   }
} /* closestFit() */

GLAPI GLint APIENTRY 
gluScaleImage(GLenum format, GLsizei widthin, GLsizei heightin,
              GLenum typein, const void* datain, GLsizei widthout,
              GLsizei heightout, GLenum typeout, void* dataout)
{
   int components;
   GLushort* beforeImage=NULL;
   GLushort* afterImage=NULL;
   PixelStorageModes psm;

   if (widthin==0 || heightin==0 || widthout==0 || heightout==0)
   {
      return 0;
   }

   if (widthin<0 || heightin<0 || widthout<0 || heightout<0)
   {
      return GLU_INVALID_VALUE;
   }

   if (!legalFormat(format) || !legalType(typein) || !legalType(typeout))
   {
      return GLU_INVALID_ENUM;
   }
   if (!isLegalFormatForPackedPixelType(format, typein))
   {
      return GLU_INVALID_OPERATION;
   }
   if (!isLegalFormatForPackedPixelType(format, typeout))
   {
      return GLU_INVALID_OPERATION;
   }
   beforeImage=malloc(image_size(widthin, heightin, format, GL_UNSIGNED_SHORT));
   afterImage=malloc(image_size(widthout, heightout, format, GL_UNSIGNED_SHORT));
   if (beforeImage==NULL || afterImage==NULL)
   {
      if (beforeImage!=NULL)
      {
         free(beforeImage);
      }
      if (afterImage!=NULL)
      {
         free(afterImage);
      }
      return GLU_OUT_OF_MEMORY;
   }

   retrieveStoreModes(&psm);
   fill_image(&psm,widthin, heightin, format, typein, 0, datain, beforeImage);
   components=elements_per_group(format, 0);
   scale_internal(components, widthin, heightin, beforeImage, widthout, heightout, afterImage);
   empty_image(&psm, widthout, heightout, format, typeout, 0, afterImage, dataout);
   free((GLbyte*)beforeImage);
   free((GLbyte*)afterImage);

   return 0;
}

/* To make swapping images less error prone */
#define __GLU_INIT_SWAP_IMAGE void* tmpImage
#define __GLU_SWAP_IMAGE(a,b) tmpImage=a; a=b; b=tmpImage;

static int gluBuild2DMipmapLevelsCore(GLenum target, GLint internalFormat,
                                      GLsizei width, GLsizei height,
                                      GLsizei widthPowerOf2,
                                      GLsizei heightPowerOf2,
                                      GLenum format, GLenum type,
                                      GLint userLevel,
                                      GLint baseLevel,GLint maxLevel,
                                      const void* data)
{
   GLint newwidth, newheight;
   GLint level, levels;
   const void* usersImage;     /* passed from user. Don't touch! */
   void* srcImage;
   void* dstImage;             /* scratch area to build mipmapped images */
   __GLU_INIT_SWAP_IMAGE;
   GLint memreq;
   GLint cmpts;

   GLint myswap_bytes, groups_per_line, element_size, group_size;
   GLint rowsize, padding;
   PixelStorageModes psm;

   assert(checkMipmapArgs(internalFormat,format,type)==0);
   assert(width>=1 && height>=1);

   srcImage=dstImage=NULL;

   newwidth=widthPowerOf2;
   newheight=heightPowerOf2;
   levels=computeLog(newwidth);
   level=computeLog(newheight);
   if (level>levels)
   {
      levels=level;
   }

   levels+=userLevel;

   retrieveStoreModes(&psm);
   myswap_bytes=psm.unpack_swap_bytes;
   cmpts=elements_per_group(format,type);
   if (psm.unpack_row_length>0)
   {
      groups_per_line=psm.unpack_row_length;
   }
   else
   {
      groups_per_line=width;
   }

   element_size=(GLint)bytes_per_element(type);
   group_size=element_size*cmpts;
   if (element_size==1)
   {
      /* Nothing to swap */
      myswap_bytes=0;
   }

   rowsize=groups_per_line*group_size;
   padding=(rowsize%psm.unpack_alignment);
   if (padding)
   {
      rowsize+=psm.unpack_alignment-padding;
   }
   usersImage=(const GLubyte*)data+psm.unpack_skip_rows*rowsize+psm.unpack_skip_pixels*group_size;

   level=userLevel;

   /* already power-of-two square */
   if (width==newwidth && height==newheight)
   {
      /* Use usersImage for level userLevel */
      if (baseLevel<=level && level<=maxLevel)
      {
         glTexImage2D(target, level, internalFormat, width,
                      height, 0, format, type, usersImage);
      }
      if (levels==0)
      {
         /* we're done. clean up and return */
         glPixelStorei(GL_UNPACK_ALIGNMENT, psm.unpack_alignment);
         return 0;
      }

      {
         int nextWidth=newwidth/2;
         int nextHeight=newheight/2;

         /* clamp to 1 */
         if (nextWidth<1)
         {
            nextWidth=1;
         }
         if (nextHeight<1)
         {
            nextHeight=1;
         }
         memreq=image_size(nextWidth, nextHeight, format, type);
      }

      switch(type)
      {
         case GL_UNSIGNED_BYTE:
              dstImage = (GLubyte *)malloc(memreq);
              break;
         case GL_UNSIGNED_SHORT_5_6_5:
         case GL_UNSIGNED_SHORT_4_4_4_4:
         case GL_UNSIGNED_SHORT_5_5_5_1:
              dstImage = (GLushort *)malloc(memreq);
              break;
         default:
              return GLU_INVALID_ENUM;
      }
      if (dstImage==NULL)
      {
         glPixelStorei(GL_UNPACK_ALIGNMENT, psm.unpack_alignment);
         return GLU_OUT_OF_MEMORY;
      }
      else
      {
         switch(type)
         {
            case GL_UNSIGNED_BYTE:
                 halveImage_ubyte(cmpts, width, height, (const GLubyte*)usersImage,
                                 (GLubyte*)dstImage, element_size, rowsize, group_size);
                 break;
            case GL_UNSIGNED_SHORT_5_6_5:
                 halveImagePackedPixel(3, extract565, shove565, width, height,
                                       usersImage, dstImage, element_size,
                                       rowsize, myswap_bytes);
                 break;
            case GL_UNSIGNED_SHORT_4_4_4_4:
                 halveImagePackedPixel(4, extract4444, shove4444, width, height,
                                       usersImage, dstImage, element_size,
                                       rowsize, myswap_bytes);
                 break;
            case GL_UNSIGNED_SHORT_5_5_5_1:
                 halveImagePackedPixel(4, extract5551, shove5551, width, height,
                                       usersImage, dstImage, element_size,
                                       rowsize, myswap_bytes);
                 break;
            default:
                 assert(0);
                 break;
         }
      }

      newwidth=width/2;
      newheight=height/2;

      /* clamp to 1 */
      if (newwidth<1)
      {
         newwidth=1;
      }
      if (newheight<1)
      {
         newheight=1;
      }

      myswap_bytes=0;
      rowsize=newwidth*group_size;
      memreq=image_size(newwidth, newheight, format, type);
      /* Swap srcImage and dstImage */
      __GLU_SWAP_IMAGE(srcImage,dstImage);
      switch(type)
      {
         case GL_UNSIGNED_BYTE:
              dstImage=(GLubyte*)malloc(memreq);
              break;
         case GL_UNSIGNED_SHORT_5_6_5:
         case GL_UNSIGNED_SHORT_4_4_4_4:
         case GL_UNSIGNED_SHORT_5_5_5_1:
              dstImage=(GLushort*)malloc(memreq);
              break;
         default:
              return GLU_INVALID_ENUM;
      }
      if (dstImage==NULL)
      {
         glPixelStorei(GL_UNPACK_ALIGNMENT, psm.unpack_alignment);
         return GLU_OUT_OF_MEMORY;
      }

      /* level userLevel+1 is in srcImage; level userLevel already saved */
      level = userLevel+1;
   }
   else
   {
      /* user's image is *not* nice power-of-2 sized square */
      memreq=image_size(newwidth, newheight, format, type);
      switch(type)
      {
         case GL_UNSIGNED_BYTE:
              dstImage=(GLubyte*)malloc(memreq);
              break;
         case GL_UNSIGNED_SHORT_5_6_5:
         case GL_UNSIGNED_SHORT_4_4_4_4:
         case GL_UNSIGNED_SHORT_5_5_5_1:
              dstImage=(GLushort*)malloc(memreq);
              break;
         default:
              return GLU_INVALID_ENUM;
      }

      if (dstImage==NULL)
      {
         glPixelStorei(GL_UNPACK_ALIGNMENT, psm.unpack_alignment);
         return GLU_OUT_OF_MEMORY;
      }

      switch(type)
      {
         case GL_UNSIGNED_BYTE:
              scale_internal_ubyte(cmpts, width, height,
                 (const GLubyte*)usersImage, newwidth, newheight,
                 (GLubyte*)dstImage, element_size, rowsize, group_size);
              break;
         case GL_UNSIGNED_SHORT_5_6_5:
              scaleInternalPackedPixel(3, extract565, shove565, width, height,
                 usersImage, newwidth, newheight, (void*)dstImage, element_size,
                 rowsize, myswap_bytes);
              break;
         case GL_UNSIGNED_SHORT_4_4_4_4:
              scaleInternalPackedPixel(4, extract4444, shove4444, width, height,
                 usersImage, newwidth, newheight, (void*)dstImage, element_size,
                 rowsize,myswap_bytes);
              break;
         case GL_UNSIGNED_SHORT_5_5_5_1:
              scaleInternalPackedPixel(4,extract5551, shove5551, width, height,
                 usersImage, newwidth, newheight, (void*)dstImage, element_size,
                 rowsize, myswap_bytes);
              break;
         default:
              assert(0);
              break;
      }
      myswap_bytes=0;
      rowsize=newwidth*group_size;

      /* Swap dstImage and srcImage */
      __GLU_SWAP_IMAGE(srcImage,dstImage);

      /* use as little memory as possible */
      if (levels!=0)
      {
         {
            int nextWidth=newwidth/2;
            int nextHeight=newheight/2;

            if (nextWidth<1)
            {
               nextWidth=1;
            }
            if (nextHeight<1)
            {
               nextHeight=1;
            }

            memreq=image_size(nextWidth, nextHeight, format, type);
         }

         switch(type)
         {
            case GL_UNSIGNED_BYTE:
                 dstImage = (GLubyte *)malloc(memreq);
                 break;
            case GL_UNSIGNED_SHORT_5_6_5:
            case GL_UNSIGNED_SHORT_4_4_4_4:
            case GL_UNSIGNED_SHORT_5_5_5_1:
                 dstImage = (GLushort *)malloc(memreq);
                 break;
            default:
                 return GLU_INVALID_ENUM;
         }
         if (dstImage==NULL)
         {
            glPixelStorei(GL_UNPACK_ALIGNMENT, psm.unpack_alignment);
            return GLU_OUT_OF_MEMORY;
         }
      }

      /* level userLevel is in srcImage; nothing saved yet */
      level=userLevel;
   }

   if (baseLevel<=level && level<=maxLevel)
   {
      glTexImage2D(target, level, internalFormat, newwidth, newheight, 0,
                   format, type, (void*)srcImage);
   }

   level++; /* update current level for the loop */
   for (; level<=levels; level++)
   {
      switch(type)
      {
         case GL_UNSIGNED_BYTE:
              halveImage_ubyte(cmpts, newwidth, newheight, (GLubyte*)srcImage,
                 (GLubyte *)dstImage, element_size, rowsize, group_size);
              break;
         case GL_UNSIGNED_SHORT_5_6_5:
              halveImagePackedPixel(3, extract565, shove565, newwidth,
                 newheight, srcImage, dstImage, element_size, rowsize,
                 myswap_bytes);
              break;
         case GL_UNSIGNED_SHORT_4_4_4_4:
              halveImagePackedPixel(4, extract4444, shove4444, newwidth,
                 newheight, srcImage, dstImage, element_size, rowsize,
                 myswap_bytes);
              break;
         case GL_UNSIGNED_SHORT_5_5_5_1:
              halveImagePackedPixel(4, extract5551, shove5551, newwidth,
                 newheight, srcImage, dstImage, element_size, rowsize,
                 myswap_bytes);
              break;
         default:
              assert(0);
              break;
      }

      __GLU_SWAP_IMAGE(srcImage,dstImage);

      if (newwidth>1)
      {
         newwidth/=2;
         rowsize/=2;
      }
      if (newheight>1)
      {
         newheight/=2;
      }

      {
         /* compute amount to pad per row, if any */
         int rowPad=rowsize%psm.unpack_alignment;

         /* should row be padded? */
         if (rowPad == 0)
         {  /* nope, row should not be padded */
            /* call tex image with srcImage untouched since it's not padded */
            if (baseLevel<=level && level<=maxLevel)
            {
               glTexImage2D(target, level, internalFormat, newwidth, newheight,
                            0, format, type, (void*) srcImage);
            }
         }
         else
         {  /* yes, row should be padded */
            /* compute length of new row in bytes, including padding */
            int newRowLength=rowsize+psm.unpack_alignment-rowPad;
            int ii;
            unsigned char* dstTrav;
            unsigned char* srcTrav; /* indices for copying */

            /* allocate new image for mipmap of size newRowLength x newheight */
            void* newMipmapImage=malloc((size_t)(newRowLength*newheight));
            if (newMipmapImage==NULL)
            {
               /* out of memory so return */
               glPixelStorei(GL_UNPACK_ALIGNMENT, psm.unpack_alignment);
               return GLU_OUT_OF_MEMORY;
            }

            /* copy image from srcImage into newMipmapImage by rows */
            for (ii=0,
                 dstTrav=(unsigned char*) newMipmapImage,
                 srcTrav=(unsigned char*) srcImage;
                 ii<newheight;
                 ii++, dstTrav+= newRowLength, /* make sure the correct distance... */
                 srcTrav+= rowsize)
            {  /* ...is skipped */
               memcpy(dstTrav, srcTrav, rowsize);
               /* note that the pad bytes are not visited and will contain
                * garbage, which is ok.
                */
            }
            /* ...and use this new image for mipmapping instead */
            if (baseLevel<=level && level<=maxLevel)
            {
               glTexImage2D(target, level, internalFormat, newwidth, newheight,
                            0, format, type, newMipmapImage);
            }
            free(newMipmapImage); /* don't forget to free it! */
         }
      }
   } /* for level */

   glPixelStorei(GL_UNPACK_ALIGNMENT, psm.unpack_alignment);

   free(srcImage); /*if you get to here, a srcImage has always been malloc'ed*/

   if (dstImage)
   { /* if it's non-rectangular and only 1 level */
      free(dstImage);
   }

   return 0;
} /* gluBuild2DMipmapLevelsCore() */

GLAPI GLint APIENTRY
gluBuild2DMipmapLevels(GLenum target, GLint internalFormat,
                       GLsizei width, GLsizei height, GLenum format,
                       GLenum type, GLint userLevel, GLint baseLevel,
                       GLint maxLevel, const void* data)
{
   int level, levels;

   int rc=checkMipmapArgs(internalFormat,format,type);

   if (rc!=0)
   {
      return rc;
   }

   if (width<1 || height<1)
   {
      return GLU_INVALID_VALUE;
   }

   levels=computeLog(width);
   level=computeLog(height);
   if (level>levels)
   {
      levels=level;
   }

   levels+=userLevel;
   if (!isLegalLevels(userLevel, baseLevel, maxLevel, levels))
   {
      return GLU_INVALID_VALUE;
   }

   return gluBuild2DMipmapLevelsCore(target, internalFormat, width, height,
                                     width, height, format, type,
                                     userLevel, baseLevel, maxLevel, data);
} /* gluBuild2DMipmapLevels() */

GLAPI GLint APIENTRY
gluBuild2DMipmaps(GLenum target, GLint internalFormat, GLsizei width,
                  GLsizei height, GLenum format, GLenum type, const void* data)
{
   GLint widthPowerOf2, heightPowerOf2;
   int level, levels;

   int rc=checkMipmapArgs(internalFormat,format,type);
   if (rc!=0)
   {
      return rc;
   }

   if (width<1 || height<1)
   {
      return GLU_INVALID_VALUE;
   }

   closestFit(target, width, height, internalFormat, format, type,
              &widthPowerOf2,&heightPowerOf2);

   levels=computeLog(widthPowerOf2);
   level=computeLog(heightPowerOf2);
   if (level>levels)
   {
      levels=level;
   }

   return gluBuild2DMipmapLevelsCore(target,internalFormat, width, height,
                                     widthPowerOf2, heightPowerOf2, format,
                                     type, 0, 0, levels, data);
}  /* gluBuild2DMipmaps() */

/*
 * Utility Routines
 */
static GLint elements_per_group(GLenum format, GLenum type)
{
   /*
    * Return the number of elements per group of a specified format
    */

   /* If the type is packedpixels then answer is 1 (ignore format) */
   if (type==GL_UNSIGNED_SHORT_5_6_5 ||
       type==GL_UNSIGNED_SHORT_4_4_4_4 ||
       type==GL_UNSIGNED_SHORT_5_5_5_1)
   {
      return 1;
   }

   /* Types are not packed pixels, so get elements per group */
   switch(format)
   {
      case GL_RGB:
           return 3;
      case GL_LUMINANCE_ALPHA:
           return 2;
      case GL_RGBA:
           return 4;
      default:
           return 1;
   }
}

static GLfloat bytes_per_element(GLenum type)
{
   /*
    * Return the number of bytes per element, based on the element type
    */
   switch(type)
   {
      case GL_UNSIGNED_BYTE:
           return(sizeof(GLubyte));
      case GL_UNSIGNED_SHORT_5_6_5:
      case GL_UNSIGNED_SHORT_4_4_4_4:
      case GL_UNSIGNED_SHORT_5_5_5_1:
           return(sizeof(GLushort));
      default:
           return 4;
   }
}

/*
** Compute memory required for internal packed array of data of given type
** and format.
*/
static GLint image_size(GLint width, GLint height, GLenum format, GLenum type)
{
   int bytes_per_row;
   int components;

   assert(width>0);
   assert(height>0);
   components=elements_per_group(format, type);

   bytes_per_row=(int)(bytes_per_element(type)*width);

   return bytes_per_row*height*components;
}

/*
** Extract array from user's data applying all pixel store modes.
** The internal format used is an array of unsigned shorts.
*/
static void fill_image(const PixelStorageModes* psm,
                       GLint width, GLint height, GLenum format,
                       GLenum type, GLboolean index_format,
                       const void* userdata, GLushort* newimage)
{
   GLint components;
   GLint element_size;
   GLint rowsize;
   GLint padding;
   GLint groups_per_line;
   GLint group_size;
   GLint elements_per_line;
   const GLubyte* start;
   const GLubyte* iter = NULL;
   GLushort* iter2;
   GLint i, j, k;
   GLint myswap_bytes;

   myswap_bytes=psm->unpack_swap_bytes;
   components=elements_per_group(format,type);
   if (psm->unpack_row_length>0)
   {
      groups_per_line = psm->unpack_row_length;
   }
   else
   {
      groups_per_line = width;
   }

   {
      element_size=(GLint)bytes_per_element(type);
      group_size=element_size*components;
      if (element_size==1)
      {
         myswap_bytes=0;
      }

      rowsize=groups_per_line*group_size;
      padding=(rowsize%psm->unpack_alignment);
      if (padding)
      {
         rowsize+=psm->unpack_alignment-padding;
      }
      start=(const GLubyte*)userdata+psm->unpack_skip_rows*rowsize+
                            psm->unpack_skip_pixels*group_size;
      elements_per_line=width*components;

      iter2=newimage;
      for (i=0; i<height; i++)
      {
         iter=start;
         for (j=0; j<elements_per_line; j++)
         {
            float extractComponents[4];

            switch (type)
            {
               case GL_UNSIGNED_BYTE:
                    *iter2++=(*iter)*257;
                    break;
               case GL_UNSIGNED_SHORT_5_6_5:
                    extract565(myswap_bytes, iter, extractComponents);
                    for (k=0; k<3; k++)
                    {
                       *iter2++=(GLushort)(extractComponents[k]*65535);
                    }
                    break;
               case GL_UNSIGNED_SHORT_4_4_4_4:
                    extract4444(myswap_bytes, iter, extractComponents);
                    for (k=0; k<4; k++)
                    {
                       *iter2++=(GLushort)(extractComponents[k]*65535);
                    }
                    break;
               case GL_UNSIGNED_SHORT_5_5_5_1:
                    extract5551(myswap_bytes, iter, extractComponents);
                    for (k = 0; k < 4; k++)
                    {
                       *iter2++=(GLushort)(extractComponents[k]*65535);
                    }
                    break;
            }
            iter+=element_size;
         } /* for j */
         start+=rowsize;
#if 1
         /* want 'iter' pointing at start, not within, row for assertion
          * purposes
          */
         iter= start;
#endif
      } /* for i */

      /* iterators should be one byte past end */
      if (!isTypePackedPixel(type))
      {
         assert(iter2==&newimage[width*height*components]);
      }
      else
      {
         assert(iter2==&newimage[width*height*elements_per_group(format, 0)]);
      }
      assert(iter==&((const GLubyte*)userdata)[rowsize*height+
             psm->unpack_skip_rows*rowsize+psm->unpack_skip_pixels*group_size]);

   }
}

/*
** Insert array into user's data applying all pixel store modes.
** The internal format is an array of unsigned shorts.
** empty_image() because it is the opposite of fill_image().
*/
static void empty_image(const PixelStorageModes* psm, GLint width,
                        GLint height, GLenum format, GLenum type,
                        GLboolean index_format, const GLushort* oldimage,
                        void* userdata)
{
   GLint components;
   GLint element_size;
   GLint rowsize;
   GLint padding;
   GLint groups_per_line;
   GLint group_size;
   GLint elements_per_line;
   GLubyte* start;
   GLubyte* iter = NULL;
   const GLushort* iter2;
   GLint i, j, k;
   GLint myswap_bytes;

   myswap_bytes=psm->pack_swap_bytes;
   components=elements_per_group(format, type);
   if (psm->pack_row_length>0)
   {
      groups_per_line = psm->pack_row_length;
   }
   else
   {
      groups_per_line = width;
   }

   {
      float shoveComponents[4];

      element_size=(GLint)bytes_per_element(type);
      group_size=element_size*components;
      if (element_size==1)
      {
         myswap_bytes = 0;
      }

      rowsize=groups_per_line*group_size;
      padding=(rowsize%psm->pack_alignment);
      if (padding)
      {
         rowsize+=psm->pack_alignment-padding;
      }
      start=(GLubyte*) userdata+psm->pack_skip_rows*rowsize+psm->pack_skip_pixels*group_size;
      elements_per_line=width*components;

      iter2=oldimage;
      for (i=0; i<height; i++)
      {
         iter=start;
         for (j=0; j<elements_per_line; j++)
         {
            Type_Widget widget;

            switch(type)
            {
               case GL_UNSIGNED_BYTE:
                    *iter=*iter2++>>8;
                    break;
               case GL_UNSIGNED_SHORT_5_6_5:
                    for (k=0; k<3; k++)
                    {
                       shoveComponents[k]=*iter2++/65535.0f;
                    }
                    shove565(shoveComponents, 0, (void*)&widget.us[0]);
                    if (myswap_bytes)
                    {
                       iter[0]=widget.ub[1];
                       iter[1]=widget.ub[0];
                    }
                    else
                    {
                       *(GLushort*)iter=widget.us[0];
                    }
                    break;
               case GL_UNSIGNED_SHORT_4_4_4_4:
                    for (k=0; k<4; k++)
                    {
                       shoveComponents[k]=*iter2++/65535.0f;
                    }
                    shove4444(shoveComponents,0,(void *)&widget.us[0]);
                    if (myswap_bytes)
                    {
                       iter[0]=widget.ub[1];
                       iter[1]=widget.ub[0];
                    }
                    else
                    {
                       *(GLushort *)iter = widget.us[0];
                    }
                    break;
               case GL_UNSIGNED_SHORT_5_5_5_1:
                    for (k=0; k<4; k++)
                    {
                       shoveComponents[k]=*iter2++/65535.0f;
                    }
                    shove5551(shoveComponents,0,(void *)&widget.us[0]);
                    if (myswap_bytes)
                    {
                       iter[0]=widget.ub[1];
                       iter[1]=widget.ub[0];
                    }
                    else
                    {
                       *(GLushort*)iter=widget.us[0];
                    }
                    break;
            }
            iter+=element_size;
         } /* for j */
         start+=rowsize;
#if 1
         /* want 'iter' pointing at start, not within, row for assertion
          * purposes
          */
         iter=start;
#endif
      } /* for i */

      /* iterators should be one byte past end */
      if (!isTypePackedPixel(type))
      {
         assert(iter2==&oldimage[width*height*components]);
      }
      else
      {
         assert(iter2==&oldimage[width*height*elements_per_group(format, 0)]);
      }
      assert(iter==&((GLubyte *)userdata)[rowsize*height+psm->pack_skip_rows*rowsize+
                   psm->pack_skip_pixels*group_size]);

   }
} /* empty_image() */

/*--------------------------------------------------------------------------
 * Decimation of packed pixel types
 *--------------------------------------------------------------------------
 */
static void extract565(int isSwap, const void* packedPixel, GLfloat extractComponents[])
{
    GLushort ushort;

   if (isSwap)
   {
      ushort=__GLU_SWAP_2_BYTES(packedPixel);
   }
   else
   {
      ushort=*(const GLushort*)packedPixel;
   }

   /* 11111000,00000000 == 0xf800 */
   /* 00000111,11100000 == 0x07e0 */
   /* 00000000,00011111 == 0x001f */

   extractComponents[0]=(float)((ushort&0xf800)>>11)/31.0f; /* 31 = 2^5-1*/
   extractComponents[1]=(float)((ushort&0x07e0)>>5)/63.0f;  /* 63 = 2^6-1*/
   extractComponents[2]=(float)((ushort&0x001f))/31.0f;
} /* extract565() */

static void shove565(const GLfloat shoveComponents[], int index, void* packedPixel)
{
   /* 11111000,00000000 == 0xf800 */
   /* 00000111,11100000 == 0x07e0 */
   /* 00000000,00011111 == 0x001f */

   assert(0.0<=shoveComponents[0] && shoveComponents[0]<=1.0);
   assert(0.0<=shoveComponents[1] && shoveComponents[1]<=1.0);
   assert(0.0<=shoveComponents[2] && shoveComponents[2]<=1.0);

   /* due to limited precision, need to round before shoving */
   ((GLushort*)packedPixel)[index]=((GLushort)((shoveComponents[0]*31)+0.5)<<11)&0xf800;
   ((GLushort*)packedPixel)[index]|=((GLushort)((shoveComponents[1]*63)+0.5)<<5)&0x07e0;
   ((GLushort*)packedPixel)[index]|=((GLushort)((shoveComponents[2]*31)+0.5))&0x001f;
} /* shove565() */

static void extract4444(int isSwap,const void* packedPixel, GLfloat extractComponents[])
{
   GLushort ushort;

   if (isSwap)
   {
      ushort=__GLU_SWAP_2_BYTES(packedPixel);
   }
   else
   {
      ushort=*(const GLushort*)packedPixel;
   }

   /* 11110000,00000000 == 0xf000 */
   /* 00001111,00000000 == 0x0f00 */
   /* 00000000,11110000 == 0x00f0 */
   /* 00000000,00001111 == 0x000f */

   extractComponents[0]=(float)((ushort&0xf000)>>12)/15.0f;/* 15=2^4-1 */
   extractComponents[1]=(float)((ushort&0x0f00)>>8)/15.0f;
   extractComponents[2]=(float)((ushort&0x00f0)>>4)/15.0f;
   extractComponents[3]=(float)((ushort&0x000f))/15.0f;
} /* extract4444() */

static void shove4444(const GLfloat shoveComponents[], int index,void* packedPixel)
{
   assert(0.0<=shoveComponents[0] && shoveComponents[0]<=1.0);
   assert(0.0<=shoveComponents[1] && shoveComponents[1]<=1.0);
   assert(0.0<=shoveComponents[2] && shoveComponents[2]<=1.0);
   assert(0.0<=shoveComponents[3] && shoveComponents[3]<=1.0);

   /* due to limited precision, need to round before shoving */
   ((GLushort*)packedPixel)[index]=((GLushort)((shoveComponents[0]*15)+0.5)<<12)&0xf000;
   ((GLushort*)packedPixel)[index]|=((GLushort)((shoveComponents[1]*15)+0.5)<<8)&0x0f00;
   ((GLushort*)packedPixel)[index]|=((GLushort)((shoveComponents[2]*15)+0.5)<<4)&0x00f0;
   ((GLushort*)packedPixel)[index]|=((GLushort)((shoveComponents[3]*15)+0.5))&0x000f;
} /* shove4444() */

static void extract5551(int isSwap,const void* packedPixel, GLfloat extractComponents[])
{
   GLushort ushort;

   if (isSwap)
   {
      ushort=__GLU_SWAP_2_BYTES(packedPixel);
   }
   else
   {
      ushort=*(const GLushort*)packedPixel;
   }

   /* 11111000,00000000 == 0xf800 */
   /* 00000111,11000000 == 0x07c0 */
   /* 00000000,00111110 == 0x003e */
   /* 00000000,00000001 == 0x0001 */

   extractComponents[0]=(float)((ushort&0xf800)>>11)/31.0f;/* 31 = 2^5-1*/
   extractComponents[1]=(float)((ushort&0x07c0)>>6)/31.0f;
   extractComponents[2]=(float)((ushort&0x003e)>>1)/31.0f;
   extractComponents[3]=(float)((ushort&0x0001));
} /* extract5551() */

static void shove5551(const GLfloat shoveComponents[], int index,void* packedPixel)
{
   /* 11111000,00000000 == 0xf800 */
   /* 00000111,11000000 == 0x07c0 */
   /* 00000000,00111110 == 0x003e */
   /* 00000000,00000001 == 0x0001 */

   assert(0.0<=shoveComponents[0] && shoveComponents[0]<=1.0);
   assert(0.0<=shoveComponents[1] && shoveComponents[1]<=1.0);
   assert(0.0<=shoveComponents[2] && shoveComponents[2]<=1.0);
   assert(0.0<=shoveComponents[3] && shoveComponents[3]<=1.0);

   /* due to limited precision, need to round before shoving */
   ((GLushort*)packedPixel)[index]=((GLushort)((shoveComponents[0]*31)+0.5)<<11)&0xf800;
   ((GLushort*)packedPixel)[index]|=((GLushort)((shoveComponents[1]*31)+0.5)<<6)&0x07c0;
   ((GLushort*)packedPixel)[index]|=((GLushort)((shoveComponents[2]*31)+0.5)<<1)&0x003e;
   ((GLushort*)packedPixel)[index]|=((GLushort)((shoveComponents[3])+0.5))&0x0001;
} /* shove5551() */

static void scaleInternalPackedPixel(int components,
                                     void (*extractPackedPixel)
                                     (int, const void*,GLfloat []),
                                     void (*shovePackedPixel)
                                     (const GLfloat [], int, void*),
                                     GLint widthIn,GLint heightIn,
                                     const void* dataIn,
                                     GLint widthOut,GLint heightOut,
                                     void* dataOut,
                                     GLint pixelSizeInBytes,
                                     GLint rowSizeInBytes, GLint isSwap)
{
   float convx;
   float convy;
   float percent;

   /* Max components in a format is 4, so... */
   float totals[4];
   float extractTotals[4], extractMoreTotals[4], shoveTotals[4];

   float area;
   int i,j,k,xindex;

   const char *temp, *temp0;
   int outindex = 0;

   int lowx_int, highx_int, lowy_int, highy_int;
   float x_percent, y_percent;
   float lowx_float, highx_float, lowy_float, highy_float;
   float convy_float, convx_float;
   int convy_int, convx_int;
   int l, m;
   const char* left;
   const char* right;

   if (widthIn==widthOut*2 && heightIn==heightOut*2)
   {
      halveImagePackedPixel(components,extractPackedPixel,shovePackedPixel,
                            widthIn, heightIn, dataIn, dataOut,
                            pixelSizeInBytes,rowSizeInBytes,isSwap);
      return;
   }
   convy=(float)heightIn/heightOut;
   convx=(float)widthIn/widthOut;
   convy_int=(int)floor(convy);
   convy_float=convy-convy_int;
   convx_int=(int)floor(convx);
   convx_float=convx-convx_int;

   area=convx*convy;

   lowy_int=0;
   lowy_float=0;
   highy_int=convy_int;
   highy_float=convy_float;

   for (i=0; i<heightOut; i++)
   {
      lowx_int=0;
      lowx_float=0;
      highx_int=convx_int;
      highx_float=convx_float;

      for (j=0; j<widthOut; j++)
      {
         /*
         ** Ok, now apply box filter to box that goes from (lowx, lowy)
         ** to (highx, highy) on input data into this pixel on output
         ** data.
         */
         totals[0]=totals[1]=totals[2]=totals[3]=0.0;

         /* calculate the value for pixels in the 1st row */
         xindex=lowx_int*pixelSizeInBytes;
         if ((highy_int>lowy_int) && (highx_int>lowx_int))
         {
            y_percent=1-lowy_float;
            temp=(const char*)dataIn+xindex+lowy_int*rowSizeInBytes;
            percent=y_percent*(1-lowx_float);
            (*extractPackedPixel)(isSwap, temp, extractTotals);
            for (k=0; k<components; k++)
            {
               totals[k]+=extractTotals[k]*percent;
            }
            left=temp;
            for (l=lowx_int+1; l<highx_int; l++)
            {
               temp+=pixelSizeInBytes;
               (*extractPackedPixel)(isSwap, temp, extractTotals);
               for (k=0; k<components; k++)
               {
                  totals[k]+=extractTotals[k]*y_percent;
               }
            }
            temp+=pixelSizeInBytes;
            right=temp;
            percent=y_percent*highx_float;
            (*extractPackedPixel)(isSwap, temp, extractTotals);
            for (k=0; k<components; k++)
            {
               totals[k]+=extractTotals[k]*percent;
            }

            /* calculate the value for pixels in the last row */
            y_percent=highy_float;
            percent=y_percent*(1-lowx_float);
            temp=(const char*)dataIn+xindex+highy_int*rowSizeInBytes;
            (*extractPackedPixel)(isSwap, temp, extractTotals);
            for (k=0; k<components; k++)
            {
               totals[k]+=extractTotals[k]*percent;
            }
            for (l=lowx_int+1; l<highx_int; l++)
            {
               temp+=pixelSizeInBytes;
               (*extractPackedPixel)(isSwap, temp, extractTotals);
               for (k=0; k<components; k++)
               {
                  totals[k]+=extractTotals[k]*y_percent;
               }
            }
            temp+=pixelSizeInBytes;
            percent=y_percent*highx_float;
            (*extractPackedPixel)(isSwap, temp, extractTotals);
            for (k=0; k<components; k++)
            {
               totals[k]+=extractTotals[k]*percent;
            }

            /* calculate the value for pixels in the 1st and last column */
            for (m=lowy_int+1; m<highy_int; m++)
            {
               left+=rowSizeInBytes;
               right+=rowSizeInBytes;
               (*extractPackedPixel)(isSwap, left, extractTotals);
               (*extractPackedPixel)(isSwap, right, extractMoreTotals);
               for (k=0; k<components; k++)
               {
                  totals[k]+=(extractTotals[k]*(1-lowx_float)+extractMoreTotals[k]*highx_float);
               }
            }
         }
         else
         {
            if (highy_int>lowy_int)
            {
               x_percent=highx_float-lowx_float;
               percent=(1-lowy_float)*x_percent;
               temp=(const char*)dataIn+xindex+lowy_int*rowSizeInBytes;
               (*extractPackedPixel)(isSwap, temp, extractTotals);
               for (k=0; k<components; k++)
               {
                  totals[k]+=extractTotals[k]*percent;
               }
               for (m=lowy_int+1; m<highy_int; m++)
               {
                  temp+=rowSizeInBytes;
                  (*extractPackedPixel)(isSwap, temp, extractTotals);
                  for (k=0; k<components; k++)
                  {
                     totals[k]+=extractTotals[k]*x_percent;
                  }
               }
               percent=x_percent*highy_float;
               temp+=rowSizeInBytes;
               (*extractPackedPixel)(isSwap, temp, extractTotals);
               for (k=0; k<components; k++)
               {
                  totals[k]+=extractTotals[k]*percent;
               }
            }
            else
            {
               if (highx_int > lowx_int)
               {
                  y_percent=highy_float-lowy_float;
                  percent=(1-lowx_float)*y_percent;
                  temp=(const char*)dataIn+xindex+lowy_int*rowSizeInBytes;
                  (*extractPackedPixel)(isSwap, temp, extractTotals);
                  for (k=0; k<components; k++)
                  {
                     totals[k]+= extractTotals[k] * percent;
                  }
                  for (l=lowx_int+1; l<highx_int; l++)
                  {
                     temp+=pixelSizeInBytes;
                     (*extractPackedPixel)(isSwap, temp, extractTotals);
                     for (k=0; k<components; k++)
                     {
                        totals[k]+=extractTotals[k]*y_percent;
                     }
                  }
                  temp+=pixelSizeInBytes;
                  percent=y_percent*highx_float;
                  (*extractPackedPixel)(isSwap, temp, extractTotals);
                  for (k=0; k<components; k++)
                  {
                     totals[k]+=extractTotals[k]*percent;
                  }
               }
               else
               {
                  percent=(highy_float-lowy_float)*(highx_float-lowx_float);
                  temp=(const char*)dataIn+xindex+lowy_int*rowSizeInBytes;
                  (*extractPackedPixel)(isSwap, temp, extractTotals);
                  for (k=0; k<components; k++)
                  {
                     totals[k]+=extractTotals[k]*percent;
                  }
               }
            }
         }

         /* this is for the pixels in the body */
         temp0=(const char*)dataIn+xindex+pixelSizeInBytes+(lowy_int+1)*rowSizeInBytes;
         for (m=lowy_int+1; m<highy_int; m++)
         {
            temp=temp0;
            for (l=lowx_int+1; l<highx_int; l++)
            {
               (*extractPackedPixel)(isSwap, temp, extractTotals);
               for (k=0; k<components; k++)
               {
                  totals[k]+=extractTotals[k];
               }
               temp+=pixelSizeInBytes;
            }
            temp0 += rowSizeInBytes;
         }

         outindex=(j+(i*widthOut));
         for (k=0; k<components; k++)
         {
            shoveTotals[k]=totals[k]/area;
         }
         (*shovePackedPixel)(shoveTotals, outindex, (void*)dataOut);
         lowx_int=highx_int;
         lowx_float=highx_float;
         highx_int+=convx_int;
         highx_float+=convx_float;
         if (highx_float>1)
         {
            highx_float-=1.0;
            highx_int++;
         }
      }
      lowy_int=highy_int;
      lowy_float=highy_float;
      highy_int+=convy_int;
      highy_float+=convy_float;

      if (highy_float>1)
      {
         highy_float-=1.0;
         highy_int++;
      }
   }

   assert(outindex==(widthOut*heightOut-1));
} /* scaleInternalPackedPixel() */

/* rowSizeInBytes is at least the width (in bytes) due to padding on
 *  inputs; not always equal. Output NEVER has row padding.
 */
static void halveImagePackedPixel(int components,
                                  void (*extractPackedPixel)
                                  (int, const void*,GLfloat []),
                                  void (*shovePackedPixel)
                                  (const GLfloat [],int, void*),
                                  GLint width, GLint height,
                                  const void* dataIn, void* dataOut,
                                  GLint pixelSizeInBytes,
                                  GLint rowSizeInBytes, GLint isSwap)
{
   /* handle case where there is only 1 column/row */
   if (width==1 || height==1)
   {
      assert(!(width==1 && height==1)); /* can't be 1x1 */
      halve1DimagePackedPixel(components,extractPackedPixel,shovePackedPixel,
                              width,height,dataIn,dataOut,pixelSizeInBytes,
                              rowSizeInBytes,isSwap);
      return;
   }

   {
      int ii, jj;

      int halfWidth=width/2;
      int halfHeight=height/2;
      const char* src=(const char*)dataIn;
      int padBytes=rowSizeInBytes-(width*pixelSizeInBytes);
      int outIndex=0;

      for (ii=0; ii<halfHeight; ii++)
      {
         for (jj=0; jj<halfWidth; jj++)
         {
#define BOX4 4
            float totals[4];              /* 4 is maximum components */
            float extractTotals[BOX4][4]; /* 4 is maximum components */
            int cc;

            (*extractPackedPixel)(isSwap, src, &extractTotals[0][0]);
            (*extractPackedPixel)(isSwap, (src+pixelSizeInBytes), &extractTotals[1][0]);
            (*extractPackedPixel)(isSwap, (src+rowSizeInBytes), &extractTotals[2][0]);
            (*extractPackedPixel)(isSwap, (src+rowSizeInBytes+pixelSizeInBytes), &extractTotals[3][0]);
            for (cc=0; cc<components; cc++)
            {
               int kk;

               /* grab 4 pixels to average */
               totals[cc]=0.0;
               for (kk=0; kk<BOX4; kk++)
               {
                  totals[cc]+=extractTotals[kk][cc];
               }
               totals[cc]/=(float)BOX4;
            }
            (*shovePackedPixel)(totals, outIndex, dataOut);

            outIndex++;
            /* skip over to next square of 4 */
            src+=pixelSizeInBytes+pixelSizeInBytes;
         }
         /* skip past pad bytes, if any, to get to next row */
         src+=padBytes;

         /* src is at beginning of a row here, but it's the second row of
          * the square block of 4 pixels that we just worked on so we
          * need to go one more row.
          * i.e.,
          *                   OO...
          *           here -->OO...
          *       but want -->OO...
          *                   OO...
          *                   ...
          */
         src+=rowSizeInBytes;
      }

      /* both pointers must reach one byte after the end */
      assert(src==&((const char*)dataIn)[rowSizeInBytes*height]);
      assert(outIndex==halfWidth*halfHeight);
   }
} /* halveImagePackedPixel() */

static void halve1DimagePackedPixel(int components,
                                    void (*extractPackedPixel)
                                    (int, const void*,GLfloat []),
                                    void (*shovePackedPixel)
                                    (const GLfloat [],int, void*),
                                    GLint width, GLint height,
                                    const void* dataIn, void* dataOut,
                                    GLint pixelSizeInBytes,
                                    GLint rowSizeInBytes, GLint isSwap)
{
   int halfWidth=width/2;
   int halfHeight=height/2;
   const char *src=(const char*)dataIn;
   int jj;

   assert(width==1 || height==1); /* must be 1D */
   assert(width!= height);        /* can't be square */

   if (height==1)
   {
      /* 1 row */
      int outIndex=0;

      assert(width!=1);           /* widthxheight can't be 1x1 */
      halfHeight=1;

      /* one horizontal row with possible pad bytes */
      for (jj=0; jj<halfWidth; jj++)
      {
#define BOX2 2
         float totals[4];               /* 4 is maximum components */
         float extractTotals[BOX2][4];  /* 4 is maximum components */
         int cc;

         /* average two at a time, instead of four */
         (*extractPackedPixel)(isSwap, src, &extractTotals[0][0]);
         (*extractPackedPixel)(isSwap, (src+pixelSizeInBytes), &extractTotals[1][0]);
         for (cc=0; cc<components; cc++)
         {
            int kk;

            /* grab 2 pixels to average */
            totals[cc]=0.0;
            for (kk=0; kk<BOX2; kk++)
            {
               totals[cc]+=extractTotals[kk][cc];
            }
            totals[cc]/=(float)BOX2;
         }
         (*shovePackedPixel)(totals, outIndex, dataOut);

         outIndex++;
         /* skip over to next group of 2 */
         src+= pixelSizeInBytes + pixelSizeInBytes;
      }

      {
         int padBytes=rowSizeInBytes-(width*pixelSizeInBytes);
         src+=padBytes;                 /* for assertion only */
      }
      assert(src==&((const char*)dataIn)[rowSizeInBytes]);
      assert(outIndex==halfWidth*halfHeight);
   }
   else
   {
      if (width==1)             /* 1 column */
      {
         int outIndex=0;

         assert(height!=1);        /* widthxheight can't be 1x1 */
         halfWidth=1;
         /* one vertical column with possible pad bytes per row */
         /* average two at a time */

         for (jj=0; jj<halfHeight; jj++)
         {
#define BOX2 2
            float totals[4];               /* 4 is maximum components */
            float extractTotals[BOX2][4];  /* 4 is maximum components */
            int cc;

            /* average two at a time, instead of four */
            (*extractPackedPixel)(isSwap, src, &extractTotals[0][0]);
            (*extractPackedPixel)(isSwap, (src+rowSizeInBytes), &extractTotals[1][0]);
            for (cc=0; cc<components; cc++)
            {
               int kk;

               /* grab 2 pixels to average */
               totals[cc]=0.0;
               for (kk=0; kk<BOX2; kk++)
               {
                  totals[cc]+=extractTotals[kk][cc];
               }
               totals[cc]/=(float)BOX2;
            }
            (*shovePackedPixel)(totals, outIndex, dataOut);

            outIndex++;
            src+=rowSizeInBytes+rowSizeInBytes; /* go to row after next */
         }

         assert(src==&((const char*)dataIn)[rowSizeInBytes*height]);
         assert(outIndex==halfWidth*halfHeight);
      }
   }
} /* halve1DimagePackedPixel() */

/*===========================================================================*/
