/* $Id: blend.c,v 1.2 2000/10/27 15:21:40 mholst Exp $ */

/*
 * Mesa 3-D graphics library
 * Version:  2.2
 * Copyright (C) 1995-1997  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.
 */

#ifdef DEBUG
#  include <assert.h>
#endif
#include <stdlib.h>
#include "alphabuf.h"
#include "blend.h"
#include "context.h"
#include "dlist.h"
#include "macros.h"
#include "pb.h"
#include "span.h"
#include "types.h"



void gl_BlendFunc( GLcontext* ctx, GLenum sfactor, GLenum dfactor )
{
   if (INSIDE_BEGIN_END(ctx)) {
      gl_error( ctx, GL_INVALID_OPERATION, "glBlendFunc" );
      return;
   }

   switch (sfactor) {
      case GL_ZERO:
      case GL_ONE:
      case GL_DST_COLOR:
      case GL_ONE_MINUS_DST_COLOR:
      case GL_SRC_ALPHA:
      case GL_ONE_MINUS_SRC_ALPHA:
      case GL_DST_ALPHA:
      case GL_ONE_MINUS_DST_ALPHA:
      case GL_SRC_ALPHA_SATURATE:
      case GL_CONSTANT_COLOR:
      case GL_ONE_MINUS_CONSTANT_COLOR:
      case GL_CONSTANT_ALPHA:
      case GL_ONE_MINUS_CONSTANT_ALPHA:
         ctx->Color.BlendSrc = sfactor;
         break;
      default:
         gl_error( ctx, GL_INVALID_ENUM, "glBlendFunc(sfactor)" );
         return;
   }

   switch (dfactor) {
      case GL_ZERO:
      case GL_ONE:
      case GL_SRC_COLOR:
      case GL_ONE_MINUS_SRC_COLOR:
      case GL_SRC_ALPHA:
      case GL_ONE_MINUS_SRC_ALPHA:
      case GL_DST_ALPHA:
      case GL_ONE_MINUS_DST_ALPHA:
      case GL_CONSTANT_COLOR:
      case GL_ONE_MINUS_CONSTANT_COLOR:
      case GL_CONSTANT_ALPHA:
      case GL_ONE_MINUS_CONSTANT_ALPHA:
         ctx->Color.BlendDst = dfactor;
         break;
      default:
         gl_error( ctx, GL_INVALID_ENUM, "glBlendFunc(dfactor)" );
   }

   ctx->NewState |= NEW_RASTER_OPS;
}



/* This is really an extension function! */
void gl_BlendEquation( GLcontext* ctx, GLenum mode )
{
   if (INSIDE_BEGIN_END(ctx)) {
      gl_error( ctx, GL_INVALID_OPERATION, "glBlendEquation" );
      return;
   }

   switch (mode) {
      case GL_MIN_EXT:
      case GL_MAX_EXT:
      case GL_LOGIC_OP:
      case GL_FUNC_ADD_EXT:
      case GL_FUNC_SUBTRACT_EXT:
      case GL_FUNC_REVERSE_SUBTRACT_EXT:
         ctx->Color.BlendEquation = mode;
         break;
      default:
         gl_error( ctx, GL_INVALID_ENUM, "glBlendEquation" );
         return;
   }

   /* This is needed to support 1.1's RGB logic ops AND
    * 1.0's blending logicops.
    */
   if (mode==GL_LOGIC_OP && ctx->Color.BlendEnabled) {
      ctx->Color.ColorLogicOpEnabled = GL_TRUE;
   }
   else {
      ctx->Color.ColorLogicOpEnabled = GL_FALSE;
   }

   ctx->NewState |= NEW_RASTER_OPS;
}



void gl_BlendColor( GLcontext* ctx, GLclampf red, GLclampf green,
		    GLclampf blue, GLclampf alpha )
{
   ctx->Color.BlendColor[0] = CLAMP( red,   0.0, 1.0 );
   ctx->Color.BlendColor[1] = CLAMP( green, 0.0, 1.0 );
   ctx->Color.BlendColor[2] = CLAMP( blue,  0.0, 1.0 );
   ctx->Color.BlendColor[3] = CLAMP( alpha, 0.0, 1.0 );
}



/*
 * Do the real work of gl_blend_span() and gl_blend_pixels().
 * Input:  n - number of pixels
 *         mask - the usual write mask
 * In/Out:  red, green, blue, alpha - the incoming and modified pixels
 * Input:  rdest, gdest, bdest, adest - the pixels from the dest color buffer
 */
static void do_blend( GLcontext* ctx, GLuint n, const GLubyte mask[],
                      GLubyte red[], GLubyte green[],
                      GLubyte blue[], GLubyte alpha[],
                      const GLubyte rdest[], const GLubyte gdest[],
                      const GLubyte bdest[], const GLubyte adest[] )
{
   GLuint i;

   /* Common cases: */

   if (ctx->Color.BlendEquation==GL_FUNC_ADD_EXT
       && ctx->Color.BlendSrc==GL_SRC_ALPHA
       && ctx->Color.BlendDst==GL_ONE_MINUS_SRC_ALPHA) {
      /* Alpha blending */
      GLfloat ascale = 256.0f * ctx->Visual->InvAlphaScale;
      GLint rmax = (GLint) ctx->Visual->RedScale;
      GLint gmax = (GLint) ctx->Visual->GreenScale;
      GLint bmax = (GLint) ctx->Visual->BlueScale;
      GLint amax = (GLint) ctx->Visual->AlphaScale;
      for (i=0;i<n;i++) {
	 if (mask[i]) {
	    GLint r, g, b, a;
            GLint t = (GLint) ( alpha[i] * ascale );  /* t in [0,256] */
            GLint s = 256 - t;
	    r = (red[i]   * t + rdest[i] * s) >> 8;
	    g = (green[i] * t + gdest[i] * s) >> 8;
	    b = (blue[i]  * t + bdest[i] * s) >> 8;
	    a = (alpha[i] * t + adest[i] * s) >> 8;
	    red[i]   = MIN2( r, rmax );
	    green[i] = MIN2( g, gmax );
	    blue[i]  = MIN2( b, bmax );
	    alpha[i] = MIN2( a, amax );
	 }
      }
   }

   /* General cases: */

   else if (ctx->Color.BlendEquation==GL_FUNC_ADD_EXT
         || ctx->Color.BlendEquation==GL_FUNC_SUBTRACT_EXT
         || ctx->Color.BlendEquation==GL_FUNC_REVERSE_SUBTRACT_EXT) {
      GLfloat rmax = ctx->Visual->RedScale;
      GLfloat gmax = ctx->Visual->GreenScale;
      GLfloat bmax = ctx->Visual->BlueScale;
      GLfloat amax = ctx->Visual->AlphaScale;
      GLfloat rscale = 1.0f / rmax;
      GLfloat gscale = 1.0f / gmax;
      GLfloat bscale = 1.0f / bmax;
      GLfloat ascale = 1.0f / amax;

      for (i=0;i<n;i++) {
	 if (mask[i]) {
	    GLint Rs, Gs, Bs, As;  /* Source colors */
	    GLint Rd, Gd, Bd, Ad;  /* Dest colors */
	    GLfloat sR, sG, sB, sA;  /* Source scaling */
	    GLfloat dR, dG, dB, dA;  /* Dest scaling */
	    GLfloat r, g, b, a;

	    /* Source Color */
	    Rs = red[i];
	    Gs = green[i];
	    Bs = blue[i];
	    As = alpha[i];

	    /* Frame buffer color */
	    Rd = rdest[i];
	    Gd = gdest[i];
	    Bd = bdest[i];
	    Ad = adest[i];

	    /* Source scaling */
	    switch (ctx->Color.BlendSrc) {
	       case GL_ZERO:
		  sR = sG = sB = sA = 0.0F;
		  break;
	       case GL_ONE:
		  sR = sG = sB = sA = 1.0F;
		  break;
	       case GL_DST_COLOR:
		  sR = (GLfloat) Rd * rscale;
		  sG = (GLfloat) Gd * gscale;
		  sB = (GLfloat) Bd * bscale;
		  sA = (GLfloat) Ad * ascale;
		  break;
	       case GL_ONE_MINUS_DST_COLOR:
		  sR = 1.0F - (GLfloat) Rd * rscale;
		  sG = 1.0F - (GLfloat) Gd * gscale;
		  sB = 1.0F - (GLfloat) Bd * bscale;
		  sA = 1.0F - (GLfloat) Ad * ascale;
		  break;
	       case GL_SRC_ALPHA:
		  sR = sG = sB = sA = (GLfloat) As * ascale;
		  break;
	       case GL_ONE_MINUS_SRC_ALPHA:
		  sR = sG = sB = sA = (GLfloat) 1.0F - (GLfloat) As * ascale;
		  break;
	       case GL_DST_ALPHA:
		  sR = sG = sB = sA =(GLfloat) Ad * ascale;
		  break;
	       case GL_ONE_MINUS_DST_ALPHA:
		  sR = sG = sB = sA = 1.0F - (GLfloat) Ad * ascale;
		  break;
	       case GL_SRC_ALPHA_SATURATE:
		  if (As < 1.0F - (GLfloat) Ad * ascale) {
		     sR = sG = sB = (GLfloat) As * ascale;
		  }
		  else {
		     sR = sG = sB = 1.0F - (GLfloat) Ad * ascale;
		  }
		  sA = 1.0;
		  break;
	       case GL_CONSTANT_COLOR:
		  sR = ctx->Color.BlendColor[0];
		  sG = ctx->Color.BlendColor[1];
		  sB = ctx->Color.BlendColor[2];
		  sA = ctx->Color.BlendColor[3];
		  break;
	       case GL_ONE_MINUS_CONSTANT_COLOR:
		  sR = 1.0F - ctx->Color.BlendColor[0];
		  sG = 1.0F - ctx->Color.BlendColor[1];
		  sB = 1.0F - ctx->Color.BlendColor[2];
		  sA = 1.0F - ctx->Color.BlendColor[3];
		  break;
	       case GL_CONSTANT_ALPHA:
		  sR = sG = sB = sA = ctx->Color.BlendColor[3];
		  break;
	       case GL_ONE_MINUS_CONSTANT_ALPHA:
		  sR = sG = sB = sA = 1.0F - ctx->Color.BlendColor[3];
		  break;
	       default:
                  /* this should never happen */
                  abort();
	    }

	    /* Dest scaling */
	    switch (ctx->Color.BlendDst) {
	       case GL_ZERO:
		  dR = dG = dB = dA = 0.0F;
		  break;
	       case GL_ONE:
		  dR = dG = dB = dA = 1.0F;
		  break;
	       case GL_SRC_COLOR:
		  dR = (GLfloat) Rs * rscale;
		  dG = (GLfloat) Gs * gscale;
		  dB = (GLfloat) Bs * bscale;
		  dA = (GLfloat) As * ascale;
		  break;
	       case GL_ONE_MINUS_SRC_COLOR:
		  dR = 1.0F - (GLfloat) Rs * rscale;
		  dG = 1.0F - (GLfloat) Gs * gscale;
		  dB = 1.0F - (GLfloat) Bs * bscale;
		  dA = 1.0F - (GLfloat) As * ascale;
		  break;
	       case GL_SRC_ALPHA:
		  dR = dG = dB = dA = (GLfloat) As * ascale;
		  break;
	       case GL_ONE_MINUS_SRC_ALPHA:
		  dR = dG = dB = dA = (GLfloat) 1.0F - (GLfloat) As * ascale;
		  break;
	       case GL_DST_ALPHA:
		  dR = dG = dB = dA = (GLfloat) Ad * ascale;
		  break;
	       case GL_ONE_MINUS_DST_ALPHA:
		  dR = dG = dB = dA = 1.0F - (GLfloat) Ad * ascale;
		  break;
	       case GL_CONSTANT_COLOR:
		  dR = ctx->Color.BlendColor[0];
		  dG = ctx->Color.BlendColor[1];
		  dB = ctx->Color.BlendColor[2];
		  dA = ctx->Color.BlendColor[3];
		  break;
	       case GL_ONE_MINUS_CONSTANT_COLOR:
		  dR = 1.0F - ctx->Color.BlendColor[0];
		  dG = 1.0F - ctx->Color.BlendColor[1];
		  dB = 1.0F - ctx->Color.BlendColor[2];
		  dA = 1.0F - ctx->Color.BlendColor[3];
		  break;
	       case GL_CONSTANT_ALPHA:
		  dR = dG = dB = dA = ctx->Color.BlendColor[3];
		  break;
	       case GL_ONE_MINUS_CONSTANT_ALPHA:
		  dR = dG = dB = dA = 1.0F - ctx->Color.BlendColor[3] * ascale;
		  break;
	       default:
                  /* this should never happen */
                  abort();
	    }

#ifdef DEBUG
	    assert( sR>= 0.0 && sR<=1.0 );
	    assert( sG>= 0.0 && sG<=1.0 );
	    assert( sB>= 0.0 && sB<=1.0 );
	    assert( sA>= 0.0 && sA<=1.0 );
	    assert( dR>= 0.0 && dR<=1.0 );
	    assert( dG>= 0.0 && dG<=1.0 );
	    assert( dB>= 0.0 && dB<=1.0 );
	    assert( dA>= 0.0 && dA<=1.0 );
#endif

	    /* compute blended color */
	    if (ctx->Color.BlendEquation==GL_FUNC_ADD_EXT) {
	       r = Rs * sR + Rd * dR;
	       g = Gs * sG + Gd * dG;
	       b = Bs * sB + Bd * dB;
	       a = As * sA + Ad * dA;
	    }
	    else if (ctx->Color.BlendEquation==GL_FUNC_SUBTRACT_EXT) {
	       r = Rs * sR - Rd * dR;
	       g = Gs * sG - Gd * dG;
	       b = Bs * sB - Bd * dB;
	       a = As * sA - Ad * dA;
	    }
	    else if (ctx->Color.BlendEquation==GL_FUNC_REVERSE_SUBTRACT_EXT) {
	       r = Rd * dR - Rs * sR;
	       g = Gd * dG - Gs * sG;
	       b = Bd * dB - Bs * sB;
	       a = Ad * dA - As * sA;
	    }
	    red[i]   = (GLint) CLAMP( r, 0.0F, rmax );
	    green[i] = (GLint) CLAMP( g, 0.0F, gmax );
	    blue[i]  = (GLint) CLAMP( b, 0.0F, bmax );
	    alpha[i] = (GLint) CLAMP( a, 0.0F, amax );
	 }
      }
   }
   else if (ctx->Color.BlendEquation==GL_MIN_EXT) {
      /* EXTENSION */
      for (i=0;i<n;i++) {
	 if (mask[i]) {
	    red[i]   = MIN2( red[i],   rdest[i] );
	    green[i] = MIN2( green[i], gdest[i] );
	    blue[i]  = MIN2( blue[i],  bdest[i] );
	    alpha[i] = MIN2( alpha[i], adest[i] );
	 }
      }
   }
   else if (ctx->Color.BlendEquation==GL_MAX_EXT) {
      /* EXTENSION */
      for (i=0;i<n;i++) {
	 if (mask[i]) {
	    red[i]   = MAX2( red[i],   rdest[i] );
	    green[i] = MAX2( green[i], gdest[i] );
	    blue[i]  = MAX2( blue[i],  bdest[i] );
	    alpha[i] = MAX2( alpha[i], adest[i] );
	 }
      }
   }

}





/*
 * Apply the blending operator to a span of pixels.
 * Input:  n - number of pixels in span
 *         x, y - location of leftmost pixel in span in window coords.
 *         mask - boolean mask indicating which pixels to blend.
 * In/Out:  red, green, blue, alpha - pixel values
 */
void gl_blend_span( GLcontext* ctx, GLuint n, GLint x, GLint y,
		    GLubyte red[], GLubyte green[],
		    GLubyte blue[], GLubyte alpha[],
		    GLubyte mask[] )
{
   GLubyte rdest[MAX_WIDTH], gdest[MAX_WIDTH];
   GLubyte bdest[MAX_WIDTH], adest[MAX_WIDTH];

   /* Check if device driver can do the work */
   if (ctx->Color.BlendEquation==GL_LOGIC_OP && !ctx->Color.SWLogicOpEnabled) {
      return;
   }

   /* Read span of current frame buffer pixels */
   gl_read_color_span( ctx, n, x, y, rdest, gdest, bdest, adest );

   do_blend( ctx, n, mask, red, green, blue, alpha, rdest, gdest, bdest, adest );
}





/*
 * Apply the blending operator to an array of pixels.
 * Input:  n - number of pixels in span
 *         x, y - array of pixel locations
 *         mask - boolean mask indicating which pixels to blend.
 * In/Out:  red, green, blue, alpha - array of pixel values
 */
void gl_blend_pixels( GLcontext* ctx,
                      GLuint n, const GLint x[], const GLint y[],
		      GLubyte red[], GLubyte green[],
		      GLubyte blue[], GLubyte alpha[],
		      GLubyte mask[] )
{
   GLubyte rdest[PB_SIZE], gdest[PB_SIZE], bdest[PB_SIZE], adest[PB_SIZE];

   /* Check if device driver can do the work */
   if (ctx->Color.BlendEquation==GL_LOGIC_OP && !ctx->Color.SWLogicOpEnabled) {
      return;
   }

   /* Read pixels from current color buffer */
   (*ctx->Driver.ReadColorPixels)( ctx, n, x, y, rdest, gdest, bdest, adest, mask );
   if (ctx->RasterMask & ALPHABUF_BIT) {
      gl_read_alpha_pixels( ctx, n, x, y, adest, mask );
   }

   do_blend( ctx, n, mask, red, green, blue, alpha, rdest, gdest, bdest, adest );
}
