/* $Id: readpix.c,v 1.5.2.1 1999/11/19 18:28:47 brianp Exp $ */

/*
 * Mesa 3-D graphics library
 * Version:  3.1
 * 
 * Copyright (C) 1999  Brian Paul   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 and this permission notice 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
 * BRIAN PAUL 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.
 */





#ifdef PC_HEADER
#include "all.h"
#else
#ifndef XFree86Server
#include <math.h>
#include <stdlib.h>
#include <string.h>
#else
#include "GL/xf86glx.h"
#endif
#include "alphabuf.h"
#include "context.h"
#include "depth.h"
#include "feedback.h"
#include "image.h"
#include "macros.h"
#include "pixel.h"
#include "readpix.h"
#include "span.h"
#include "stencil.h"
#include "types.h"
#endif




/*
 * Read a block of color index pixels.
 */
static void read_index_pixels( GLcontext *ctx,
                               GLint x, GLint y,
			       GLsizei width, GLsizei height,
			       GLenum type, GLvoid *pixels,
                               const struct gl_pixelstore_attrib *packing )
{
   GLint i, j, readWidth;

   /* error checking */
   if (ctx->Visual->RGBAflag) {
      gl_error( ctx, GL_INVALID_OPERATION, "glReadPixels" );
      return;
   }

   (void) (*ctx->Driver.SetBuffer)( ctx, ctx->Pixel.DriverReadBuffer );

   readWidth = (width > MAX_WIDTH) ? MAX_WIDTH : width;

   /* process image row by row */
   for (j=0;j<height;j++,y++) {
      GLuint index[MAX_WIDTH];
      GLvoid *dest;

      (*ctx->Driver.ReadCI32Span)( ctx, readWidth, x, y, index );

      if (ctx->Pixel.IndexShift!=0 || ctx->Pixel.IndexOffset!=0) {
         gl_shift_and_offset_ci( ctx, readWidth, index);
      }

      if (ctx->Pixel.MapColorFlag) {
         gl_map_ci(ctx, readWidth, index);
      }

      dest = gl_pixel_addr_in_image(packing, pixels,
                         width, height, GL_COLOR_INDEX, type, 0, j, 0);

      switch (type) {
	 case GL_UNSIGNED_BYTE:
	    {
               GLubyte *dst = (GLubyte *) dest;
	       for (i=0;i<readWidth;i++) {
		  *dst++ = (GLubyte) index[i];
	       }
	    }
	    break;
	 case GL_BYTE:
	    {
               GLbyte *dst = (GLbyte *) dest;
	       for (i=0;i<readWidth;i++) {
		  dst[i] = (GLbyte) index[i];
	       }
	    }
	    break;
	 case GL_UNSIGNED_SHORT:
	    {
               GLushort *dst = (GLushort *) dest;
	       for (i=0;i<readWidth;i++) {
		  dst[i] = (GLushort) index[i];
	       }
	       if (packing->SwapBytes) {
		  gl_swap2( (GLushort *) dst, readWidth );
	       }
	    }
	    break;
	 case GL_SHORT:
	    {
               GLshort *dst = (GLshort *) dest;
	       for (i=0;i<readWidth;i++) {
		  dst[i] = (GLshort) index[i];
	       }
	       if (packing->SwapBytes) {
		  gl_swap2( (GLushort *) dst, readWidth );
	       }
	    }
	    break;
	 case GL_UNSIGNED_INT:
	    {
               GLuint *dst = (GLuint *) dest;
	       for (i=0;i<readWidth;i++) {
		  dst[i] = (GLuint) index[i];
	       }
	       if (packing->SwapBytes) {
		  gl_swap4( (GLuint *) dst, readWidth );
	       }
	    }
	    break;
	 case GL_INT:
	    {
               GLint *dst = (GLint *) dest;
	       for (i=0;i<readWidth;i++) {
		  dst[i] = (GLint) index[i];
	       }
	       if (packing->SwapBytes) {
		  gl_swap4( (GLuint *) dst, readWidth );
	       }
	    }
	    break;
	 case GL_FLOAT:
	    {
               GLfloat *dst = (GLfloat *) dest;
	       for (i=0;i<readWidth;i++) {
		  dst[i] = (GLfloat) index[i];
	       }
	       if (packing->SwapBytes) {
		  gl_swap4( (GLuint *) dst, readWidth );
	       }
	    }
	    break;
         default:
            gl_error( ctx, GL_INVALID_ENUM, "glReadPixels(type)" );
            j = height + 1; /* exit loop */
      }
   }

   (void) (*ctx->Driver.SetBuffer)( ctx, ctx->Color.DriverDrawBuffer );
}



static void read_depth_pixels( GLcontext *ctx,
                               GLint x, GLint y,
			       GLsizei width, GLsizei height,
			       GLenum type, GLvoid *pixels,
                               const struct gl_pixelstore_attrib *packing )
{
   GLint i, j, readWidth;
   GLboolean bias_or_scale;

   /* Error checking */
   if (ctx->Visual->DepthBits <= 0) {
      /* No depth buffer */
      gl_error( ctx, GL_INVALID_OPERATION, "glReadPixels" );
      return;
   }

   readWidth = (width > MAX_WIDTH) ? MAX_WIDTH : width;

   if (type != GL_BYTE &&
       type != GL_UNSIGNED_BYTE &&
       type != GL_SHORT &&
       type != GL_UNSIGNED_SHORT &&
       type != GL_INT &&
       type != GL_UNSIGNED_INT &&
       type != GL_FLOAT) {
      gl_error( ctx, GL_INVALID_OPERATION, "glReadPixels(depth type)");
      return;
   }

   bias_or_scale = ctx->Pixel.DepthBias!=0.0 || ctx->Pixel.DepthScale!=1.0;

   if (type==GL_UNSIGNED_SHORT && sizeof(GLdepth)==sizeof(GLushort)
       && !bias_or_scale && !packing->SwapBytes) {
      /* Special case: directly read 16-bit unsigned depth values. */
      for (j=0;j<height;j++,y++) {
         GLushort *dst = (GLushort*) gl_pixel_addr_in_image( packing, pixels,
                         width, height, GL_DEPTH_COMPONENT, type, 0, j, 0 );
         (*ctx->Driver.ReadDepthSpanInt)( ctx, width, x, y, (GLdepth*) dst);
      }
   }
   else if (type==GL_UNSIGNED_INT && sizeof(GLdepth)==sizeof(GLuint)
            && !bias_or_scale && !packing->SwapBytes) {
      /* Special case: directly read 32-bit unsigned depth values. */
      /* Compute shift value to scale depth values up to 32-bit uints. */
      GLuint shift = 0;
      GLuint max = MAX_DEPTH;
      while ((max&0x80000000)==0) {
         max = max << 1;
         shift++;
      }
      for (j=0;j<height;j++,y++) {
         GLuint *dst = (GLuint *) gl_pixel_addr_in_image( packing, pixels,
                         width, height, GL_DEPTH_COMPONENT, type, 0, j, 0 );
         (*ctx->Driver.ReadDepthSpanInt)( ctx, width, x, y, (GLdepth*) dst);
         for (i=0;i<width;i++) {
            dst[i] = dst[i] << shift;
         }
      }
   }
   else {
      /* General case (slow) */
      for (j=0;j<height;j++,y++) {
         GLfloat depth[MAX_WIDTH];
         GLvoid *dest;

         (*ctx->Driver.ReadDepthSpanFloat)( ctx, readWidth, x, y, depth );

         if (bias_or_scale) {
            for (i=0;i<readWidth;i++) {
               GLfloat d;
               d = depth[i] * ctx->Pixel.DepthScale + ctx->Pixel.DepthBias;
               depth[i] = CLAMP( d, 0.0F, 1.0F );
            }
         }

         dest = gl_pixel_addr_in_image(packing, pixels,
                         width, height, GL_DEPTH_COMPONENT, type, 0, j, 0);

         switch (type) {
            case GL_UNSIGNED_BYTE:
               {
                  GLubyte *dst = (GLubyte *) dest;
                  for (i=0;i<readWidth;i++) {
                     dst[i] = FLOAT_TO_UBYTE( depth[i] );
                  }
               }
               break;
            case GL_BYTE:
               {
                  GLbyte *dst = (GLbyte *) dest;
                  for (i=0;i<readWidth;i++) {
                     dst[i] = FLOAT_TO_BYTE( depth[i] );
                  }
               }
               break;
            case GL_UNSIGNED_SHORT:
               {
                  GLushort *dst = (GLushort *) dest;
                  for (i=0;i<readWidth;i++) {
                     dst[i] = FLOAT_TO_USHORT( depth[i] );
                  }
                  if (packing->SwapBytes) {
                     gl_swap2( (GLushort *) dst, readWidth );
                  }
               }
               break;
            case GL_SHORT:
               {
                  GLshort *dst = (GLshort *) dest;
                  for (i=0;i<readWidth;i++) {
                     dst[i] = FLOAT_TO_SHORT( depth[i] );
                  }
                  if (packing->SwapBytes) {
                     gl_swap2( (GLushort *) dst, readWidth );
                  }
               }
               break;
            case GL_UNSIGNED_INT:
               {
                  GLuint *dst = (GLuint *) dest;
                  for (i=0;i<readWidth;i++) {
                     dst[i] = FLOAT_TO_UINT( depth[i] );
                  }
                  if (packing->SwapBytes) {
                     gl_swap4( (GLuint *) dst, readWidth );
                  }
               }
               break;
            case GL_INT:
               {
                  GLint *dst = (GLint *) dest;
                  for (i=0;i<readWidth;i++) {
                     dst[i] = FLOAT_TO_INT( depth[i] );
                  }
                  if (packing->SwapBytes) {
                     gl_swap4( (GLuint *) dst, readWidth );
                  }
               }
               break;
            case GL_FLOAT:
               {
                  GLfloat *dst = (GLfloat *) dest;
                  for (i=0;i<readWidth;i++) {
                     dst[i] = depth[i];
                  }
                  if (packing->SwapBytes) {
                     gl_swap4( (GLuint *) dst, readWidth );
                  }
               }
               break;
            default:
               gl_error( ctx, GL_INVALID_ENUM, "glReadPixels(type)" );
         }
      }
   }
}




static void read_stencil_pixels( GLcontext *ctx,
                                 GLint x, GLint y,
				 GLsizei width, GLsizei height,
				 GLenum type, GLvoid *pixels,
                                 const struct gl_pixelstore_attrib *packing )
{
   GLboolean shift_or_offset;
   GLint i, j, readWidth;

   if (type != GL_BYTE &&
       type != GL_UNSIGNED_BYTE &&
       type != GL_SHORT &&
       type != GL_UNSIGNED_SHORT &&
       type != GL_INT &&
       type != GL_UNSIGNED_INT &&
       type != GL_FLOAT &&
       type != GL_BITMAP) {
      gl_error( ctx, GL_INVALID_OPERATION, "glReadPixels(stencil type)");
      return;
   }

   readWidth = (width > MAX_WIDTH) ? MAX_WIDTH : width;

   if (ctx->Visual->StencilBits<=0) {
      /* No stencil buffer */
      gl_error( ctx, GL_INVALID_OPERATION, "glReadPixels" );
      return;
   }

   shift_or_offset = ctx->Pixel.IndexShift!=0 || ctx->Pixel.IndexOffset!=0;

   /* process image row by row */
   for (j=0;j<height;j++,y++) {
      GLvoid *dest;
      GLstencil stencil[MAX_WIDTH];

      gl_read_stencil_span( ctx, readWidth, x, y, stencil );

      if (shift_or_offset) {
         gl_shift_and_offset_stencil( ctx, readWidth, stencil );
      }

      if (ctx->Pixel.MapStencilFlag) {
         gl_map_stencil( ctx, readWidth, stencil );
      }

      dest = gl_pixel_addr_in_image( packing, pixels,
                          width, height, GL_STENCIL_INDEX, type, 0, j, 0 );

      switch (type) {
	 case GL_UNSIGNED_BYTE:
            if (sizeof(GLstencil) == 8) {
               MEMCPY( dest, stencil, readWidth );
            }
            else {
               GLubyte *dst = (GLubyte *) dest;
	       for (i=0;i<readWidth;i++) {
		  dst[i] = (GLubyte) stencil[i];
	       }
            }
	    break;
	 case GL_BYTE:
            if (sizeof(GLstencil) == 8) {
               MEMCPY( dest, stencil, readWidth );
            }
            else {
               GLbyte *dst = (GLbyte *) dest;
	       for (i=0;i<readWidth;i++) {
		  dst[i] = (GLbyte) stencil[i];
	       }
            }
	    break;
	 case GL_UNSIGNED_SHORT:
	    {
               GLushort *dst = (GLushort *) dest;
	       for (i=0;i<readWidth;i++) {
		  dst[i] = (GLushort) stencil[i];
	       }
	       if (packing->SwapBytes) {
		  gl_swap2( (GLushort *) dst, readWidth );
	       }
	    }
	    break;
	 case GL_SHORT:
	    {
               GLshort *dst = (GLshort *) dest;
	       for (i=0;i<readWidth;i++) {
		  dst[i] = (GLshort) stencil[i];
	       }
	       if (packing->SwapBytes) {
		  gl_swap2( (GLushort *) dst, readWidth );
	       }
	    }
	    break;
	 case GL_UNSIGNED_INT:
	    {
               GLuint *dst = (GLuint *) dest;
	       for (i=0;i<readWidth;i++) {
		  dst[i] = (GLuint) stencil[i];
	       }
	       if (packing->SwapBytes) {
		  gl_swap4( (GLuint *) dst, readWidth );
	       }
	    }
	    break;
	 case GL_INT:
	    {
               GLint *dst = (GLint *) dest;
	       for (i=0;i<readWidth;i++) {
		  *dst++ = (GLint) stencil[i];
	       }
	       if (packing->SwapBytes) {
		  gl_swap4( (GLuint *) dst, readWidth );
	       }
	    }
	    break;
	 case GL_FLOAT:
	    {
               GLfloat *dst = (GLfloat *) dest;
	       for (i=0;i<readWidth;i++) {
		  dst[i] = (GLfloat) stencil[i];
	       }
	       if (packing->SwapBytes) {
		  gl_swap4( (GLuint *) dst, readWidth );
	       }
	    }
	    break;
         case GL_BITMAP:
            if (packing->LsbFirst) {
               GLubyte *dst = (GLubyte*) dest;
               GLint shift = 0;
               for (i = 0; i < readWidth; i++) {
                  if (shift == 0)
                     *dst = 0;
                  *dst |= ((stencil != 0) << shift);
                  shift++;
                  if (shift == 8) {
                     shift = 0;
                     dst++;
                  }
               }
            }
            else {
               GLubyte *dst = (GLubyte*) dest;
               GLint shift = 7;
               for (i = 0; i < readWidth; i++) {
                  if (shift == 7)
                     *dst = 0;
                  *dst |= ((stencil != 0) << shift);
                  shift--;
                  if (shift < 0) {
                     shift = 7;
                     dst++;
                  }
               }
            }
            break;
         default:
            gl_error( ctx, GL_INVALID_ENUM, "glReadPixels(type)" );
      }
   }
}



/*
 * Optimized glReadPixels for particular pixel formats:
 *   GL_UNSIGNED_BYTE, GL_RGBA
 * when pixel scaling, biasing and mapping are disabled.
 */
static GLboolean
read_fast_rgba_pixels( GLcontext *ctx,
                       GLint x, GLint y,
                       GLsizei width, GLsizei height,
                       GLenum format, GLenum type,
                       GLvoid *pixels,
                       const struct gl_pixelstore_attrib *packing )
{
   /* can't do scale, bias or mapping */
   if (ctx->Pixel.ScaleOrBiasRGBA || ctx->Pixel.MapColorFlag)
       return GL_FALSE;

   /* can't do fancy pixel packing */
   if (packing->Alignment != 1 || packing->SwapBytes || packing->LsbFirst)
      return GL_FALSE;

   {
      GLint srcX = x;
      GLint srcY = y;
      GLint readWidth = width;           /* actual width read */
      GLint readHeight = height;         /* actual height read */
      GLint skipPixels = packing->SkipPixels;
      GLint skipRows = packing->SkipRows;
      GLint rowLength;

      if (packing->RowLength > 0)
         rowLength = packing->RowLength;
      else
         rowLength = width;

      /* horizontal clipping */
      if (srcX < ctx->Buffer->Xmin) {
         skipPixels += (ctx->Buffer->Xmin - srcX);
         readWidth  -= (ctx->Buffer->Xmin - srcX);
         srcX = ctx->Buffer->Xmin;
      }
      if (srcX + readWidth > ctx->Buffer->Xmax)
         readWidth -= (srcX + readWidth - ctx->Buffer->Xmax - 1);
      if (readWidth <= 0)
         return GL_TRUE;

      /* vertical clipping */
      if (srcY < ctx->Buffer->Ymin) {
         skipRows   += (ctx->Buffer->Ymin - srcY);
         readHeight -= (ctx->Buffer->Ymin - srcY);
         srcY = ctx->Buffer->Ymin;
      }
      if (srcY + readHeight > ctx->Buffer->Ymax)
         readHeight -= (srcY + readHeight - ctx->Buffer->Ymax - 1);
      if (readHeight <= 0)
         return GL_TRUE;

      /*
       * Ready to read!
       * The window region at (destX, destY) of size (readWidth, readHeight)
       * will be read back.
       * We'll write pixel data to buffer pointed to by "pixels" but we'll
       * skip "skipRows" rows and skip "skipPixels" pixels/row.
       */
      if (format==GL_RGBA && type==GL_UNSIGNED_BYTE) {
         GLubyte *dest = (GLubyte *) pixels
                         + (skipRows * rowLength + skipPixels) * 4;
         GLint row;
         for (row=0; row<readHeight; row++) {
            (*ctx->Driver.ReadRGBASpan)(ctx, readWidth, srcX, srcY,
                                        (GLubyte (*)[4]) dest);
            if (ctx->Visual->SoftwareAlpha) {
               gl_read_alpha_span(ctx, readWidth, srcX, srcY,
                                  (GLubyte (*)[4]) dest);
            }
            dest += rowLength * 4;
            srcY++;
         }
         return GL_TRUE;
      }
      else {
         /* can't do this format/type combination */
         return GL_FALSE;
      }
   }
}



/*
 * Read R, G, B, A, RGB, L, or LA pixels.
 */
static void read_rgba_pixels( GLcontext *ctx,
                              GLint x, GLint y,
                              GLsizei width, GLsizei height,
                              GLenum format, GLenum type, GLvoid *pixels,
                              const struct gl_pixelstore_attrib *packing )
{
   GLint readWidth;

   (void) (*ctx->Driver.SetBuffer)( ctx, ctx->Pixel.DriverReadBuffer );

   /* Try optimized path first */
   if (read_fast_rgba_pixels( ctx, x, y, width, height,
                              format, type, pixels, packing )) {

      (void) (*ctx->Driver.SetBuffer)( ctx, ctx->Color.DriverDrawBuffer );
      return; /* done! */
   }

   readWidth = (width > MAX_WIDTH) ? MAX_WIDTH : width;

   /* do error checking on pixel type, format was already checked by caller */
   switch (type) {
      case GL_UNSIGNED_BYTE:
      case GL_BYTE:
      case GL_UNSIGNED_SHORT:
      case GL_SHORT:
      case GL_UNSIGNED_INT:
      case GL_INT:
      case GL_FLOAT:
      case GL_UNSIGNED_BYTE_3_3_2:
      case GL_UNSIGNED_BYTE_2_3_3_REV:
      case GL_UNSIGNED_SHORT_5_6_5:
      case GL_UNSIGNED_SHORT_5_6_5_REV:
      case GL_UNSIGNED_SHORT_4_4_4_4:
      case GL_UNSIGNED_SHORT_4_4_4_4_REV:
      case GL_UNSIGNED_SHORT_5_5_5_1:
      case GL_UNSIGNED_SHORT_1_5_5_5_REV:
      case GL_UNSIGNED_INT_8_8_8_8:
      case GL_UNSIGNED_INT_8_8_8_8_REV:
      case GL_UNSIGNED_INT_10_10_10_2:
      case GL_UNSIGNED_INT_2_10_10_10_REV:
         /* valid pixel type */
         break;
      default:
         gl_error( ctx, GL_INVALID_ENUM, "glReadPixels(type)" );
         return;
   }

   if (!gl_is_legal_format_and_type(format, type)) {
      gl_error(ctx, GL_INVALID_OPERATION, "glReadPixels(format or type)");
      return;
   }

   if (ctx->Visual->RGBAflag) {
      GLint j;
      for (j=0;j<height;j++,y++) {
         GLubyte rgba[MAX_WIDTH][4];
         GLvoid *dest;

         gl_read_rgba_span( ctx, readWidth, x, y, rgba );

         dest = gl_pixel_addr_in_image( packing, pixels, width, height,
                                        format, type, 0, j, 0);

         gl_pack_rgba_span( ctx, readWidth, (const GLubyte (*)[4]) rgba,
                            format, type, dest, packing, GL_TRUE );
      }
   }
   else {
      GLint j;
      for (j=0;j<height;j++,y++) {
         GLubyte rgba[MAX_WIDTH][4];
	 GLuint index[MAX_WIDTH];
         GLvoid *dest;

	 (*ctx->Driver.ReadCI32Span)( ctx, readWidth, x, y, index );

	 if (ctx->Pixel.IndexShift!=0 || ctx->Pixel.IndexOffset!=0) {
            gl_map_ci( ctx, readWidth, index );
	 }

         gl_map_ci_to_rgba(ctx, readWidth, index, rgba );

         dest = gl_pixel_addr_in_image( packing, pixels, width, height,
                                        format, type, 0, j, 0);

         gl_pack_rgba_span( ctx, readWidth, (const GLubyte (*)[4]) rgba,
                            format, type, dest, packing, GL_TRUE );
      }
   }

   (void) (*ctx->Driver.SetBuffer)( ctx, ctx->Color.DriverDrawBuffer );
}



void gl_ReadPixels( GLcontext *ctx,
                    GLint x, GLint y, GLsizei width, GLsizei height,
		    GLenum format, GLenum type, GLvoid *pixels )
{
   ASSERT_OUTSIDE_BEGIN_END_AND_FLUSH(ctx, "glReadPixels");

   if (!pixels) {
      gl_error( ctx, GL_INVALID_VALUE, "glReadPixels(pixels)" );
      return;
   }

   switch (format) {
      case GL_COLOR_INDEX:
         read_index_pixels( ctx, x, y, width, height, type, pixels, &ctx->Pack );
	 break;
      case GL_STENCIL_INDEX:
	 read_stencil_pixels( ctx, x, y, width, height, type, pixels, &ctx->Pack );
         break;
      case GL_DEPTH_COMPONENT:
	 read_depth_pixels( ctx, x, y, width, height, type, pixels, &ctx->Pack );
	 break;
      case GL_RED:
      case GL_GREEN:
      case GL_BLUE:
      case GL_ALPHA:
      case GL_RGB:
      case GL_LUMINANCE:
      case GL_LUMINANCE_ALPHA:
      case GL_RGBA:
      case GL_BGR:
      case GL_BGRA:
      case GL_ABGR_EXT:
         read_rgba_pixels( ctx, x, y, width, height, format, type, pixels, &ctx->Pack );
	 break;
      default:
	 gl_error( ctx, GL_INVALID_ENUM, "glReadPixels(format)" );
   }
}
