#include "img_conv.h"
#include "Icon.h"

#ifdef __cplusplus
extern "C" {
#endif

typedef void BitBltProc( Byte * src, Byte * dst, int count);
typedef BitBltProc *PBitBltProc;

static void
bitblt_copy( Byte * src, Byte * dst, int count)
{
   memcpy( dst, src, count);
}

static void
bitblt_move( Byte * src, Byte * dst, int count)
{
   memmove( dst, src, count);
}

static void
bitblt_or( Byte * src, Byte * dst, int count)
{
   while ( count--) *(dst++) |= *(src++);
}

static void
bitblt_and( Byte * src, Byte * dst, int count)
{
   while ( count--) *(dst++) &= *(src++);
}

static void
bitblt_xor( Byte * src, Byte * dst, int count)
{
   while ( count--) *(dst++) ^= *(src++);
}

static void
bitblt_not( Byte * src, Byte * dst, int count)
{
   while ( count--) *(dst++) = ~(*(src++));
}

static void
bitblt_notdstand( Byte * src, Byte * dst, int count)
{
   while ( count--) {
      *dst = ~(*dst) & (*(src++));
      dst++;
   }
}

static void
bitblt_notdstor( Byte * src, Byte * dst, int count)
{
   while ( count--) {
      *dst = ~(*dst) | (*(src++));
      dst++;
   }
}

static void
bitblt_notsrcand( Byte * src, Byte * dst, int count)
{
   while ( count--) *(dst++) &= ~(*(src++));
}

static void
bitblt_notsrcor( Byte * src, Byte * dst, int count)
{
   while ( count--) *(dst++) |= ~(*(src++));
}

static void
bitblt_notxor( Byte * src, Byte * dst, int count)
{
   while ( count--) {
      *dst = ~( *(src++) ^ (*dst));
      dst++;
   }
}

static void
bitblt_notand( Byte * src, Byte * dst, int count)
{
   while ( count--) {
      *dst = ~( *(src++) & (*dst));
      dst++;
   }
}

static void
bitblt_notor( Byte * src, Byte * dst, int count)
{
   while ( count--) {
      *dst = ~( *(src++) | (*dst));
      dst++;
   }
}

static void
bitblt_black( Byte * src, Byte * dst, int count)
{
   memset( dst, 0, count);
}

static void
bitblt_white( Byte * src, Byte * dst, int count)
{
   memset( dst, 0xff, count);
}

static void
bitblt_invert( Byte * src, Byte * dst, int count)
{
   while ( count--) {
      *dst = ~(*dst);
      dst++;
   }
}

static PBitBltProc
find_blt_proc( int rop )
{
   PBitBltProc proc = NULL;
   switch ( rop) {
   case ropCopyPut:
      proc = bitblt_copy;
      break;
   case ropAndPut:
      proc = bitblt_and;
      break;
   case ropOrPut:
      proc = bitblt_or;
      break;
   case ropXorPut:
      proc = bitblt_xor;
      break;
   case ropNotPut:
      proc = bitblt_not;
      break;
   case ropNotDestAnd:
      proc = bitblt_notdstand;
      break;
   case ropNotDestOr:
      proc = bitblt_notdstor;
      break;
   case ropNotSrcAnd:
      proc = bitblt_notsrcand;
      break;
   case ropNotSrcOr:
      proc = bitblt_notsrcor;
      break;
   case ropNotXor:
      proc = bitblt_notxor;
      break;
   case ropNotAnd:
      proc = bitblt_notand;
      break;
   case ropNotOr:
      proc = bitblt_notor;
      break;
   case ropBlackness:
      proc = bitblt_black;
      break;
   case ropWhiteness:
      proc = bitblt_white;
      break;
   case ropInvert:
      proc = bitblt_invert;
      break;
   default:
      proc = bitblt_copy;
   }
   return proc;
}

static Bool 
img_put_alpha( Handle dest, Handle src, int dstX, int dstY, int srcX, int srcY, int dstW, int dstH, int srcW, int srcH, int rop);

Bool 
img_put( Handle dest, Handle src, int dstX, int dstY, int srcX, int srcY, int dstW, int dstH, int srcW, int srcH, int rop)
{
   Point srcSz, dstSz;
   int asrcW, asrcH;
   Bool newObject = false;

   if ( dest == nilHandle || src == nilHandle) return false;
   if ( rop == ropNoOper) return false;

   if ( kind_of( src, CIcon)) {
      /* since src is always treated as read-only, 
         employ a nasty hack here, re-assigning
         all mask values to data */
      Byte * data  = PImage( src)-> data;
      int dataSize = PImage( src)-> dataSize; 
      int lineSize = PImage( src)-> lineSize; 
      int palSize  = PImage( src)-> palSize; 
      int type     = PImage( src)-> type;
      void *self   = PImage( src)-> self; 
      RGBColor palette[2];
      Byte *p;

      if ( PIcon(src)-> maskType != imbpp1) {
         if ( PIcon(src)-> maskType != imbpp8) croak("panic: bad icon mask type");
         return img_put_alpha( dest, src, dstX, dstY, srcX, srcY, dstW, dstH, srcW, srcH, rop);
      }

      PImage( src)-> self     =  CImage;
      PIcon( src)-> data = PIcon( src)-> mask;
      PImage( src)-> lineSize =  PIcon( src)-> maskLine;
      PImage( src)-> dataSize =  PIcon( src)-> maskSize;
      memcpy( palette, PImage( src)-> palette, 6);
      memcpy( PImage( src)-> palette, stdmono_palette, 6);


      PImage( src)-> type     =  imBW;
      PImage( src)-> palSize  = 2;
      img_put( dest, src, dstX, dstY, srcX, srcY, dstW, dstH, srcW, srcH, ropAndPut);
      rop = ropXorPut;
      memcpy( PImage( src)-> palette, palette, 6);

      PImage( src)-> self     = self;
      PImage( src)-> type     = type;
      PImage( src)-> data     = data; 
      PImage( src)-> lineSize = lineSize; 
      PImage( src)-> dataSize = dataSize; 
      PImage( src)-> palSize  = palSize;
   } else if ( rop == ropAlphaCopy ) {
      Bool ok;
      Image dummy;
      PIcon i;
      if ( !kind_of( dest, CIcon )) return false;
      if ( PImage(src)-> type != imByte ) {
         Handle dup = CImage(src)->dup(src);
	 CImage(dup)->set_type(src, imByte);
         ok = img_put( dest, dup, dstX, dstY, srcX, srcY, dstW, dstH, srcW, srcH, rop);
	 Object_destroy(dup);
	 return ok;
      }
      if ( PIcon(dest)-> maskType != imbpp8) {
         CIcon(dest)-> set_maskType(dest, imbpp8);
         ok = img_put( dest, src, dstX, dstY, srcX, srcY, dstW, dstH, srcW, srcH, rop);
         if ( PIcon(dest)-> options. optPreserveType )
            CIcon(dest)-> set_maskType(dest, imbpp1);
	 return ok;
      }

      i = (PIcon) dest;
      img_fill_dummy( &dummy, i-> w, i-> h, imByte, i-> mask, NULL);
      return img_put((Handle)&dummy, src, dstX, dstY, srcX, srcY, dstW, dstH, srcW, srcH, ropCopyPut);
   } else if ( rop & ropConstantAlpha )
      return img_put_alpha( dest, src, dstX, dstY, srcX, srcY, dstW, dstH, srcW, srcH, rop);
   
   srcSz. x = PImage(src)-> w;
   srcSz. y = PImage(src)-> h;
   dstSz. x = PImage(dest)-> w;
   dstSz. y = PImage(dest)-> h;

   if ( dstW < 0) {
      dstW = abs( dstW);
      srcW = -srcW;
   }
   if ( dstH < 0) {
      dstH = abs( dstH);
      srcH = -srcH;
   }
   
   asrcW = abs( srcW);
   asrcH = abs( srcH);

   if ( srcX >= srcSz. x || srcX + srcW <= 0 ||
        srcY >= srcSz. y || srcY + srcH <= 0 ||
        dstX >= dstSz. x || dstX + dstW <= 0 ||
        dstY >= dstSz. y || dstY + dstH <= 0)
      return true;

   /* check if we can do it without expensive scalings and extractions */
   if ( 
         ( srcW == dstW) && ( srcH == dstH) &&
         ( srcX >= 0) && ( srcY >= 0) && ( srcX + srcW <= srcSz. x) && ( srcY + srcH <= srcSz. y) 
      ) 
      goto NOSCALE;

   if ( srcX != 0 || srcY != 0 || asrcW != srcSz. x || asrcH != srcSz. y) {
     /* extract source rectangle */
      Handle x;
      int ssx = srcX, ssy = srcY, ssw = asrcW, ssh = asrcH;
      if ( ssx < 0) {
         ssw += ssx;
         ssx = 0;
      }
      if ( ssy < 0) {
         ssh += ssy;
         ssy = 0;
      }
      x = CImage( src)-> extract( src, ssx, ssy, ssw, ssh);
      if ( !x) return false;

      if ( srcX < 0 || srcY < 0 || srcX + asrcW >= srcSz. x || srcY + asrcH > srcSz. y) {
         HV * profile;
         Handle dx;
         int dsx = 0, dsy = 0, dsw = PImage(x)-> w, dsh = PImage(x)-> h, type = PImage( dest)-> type;

         if ( asrcW != srcW || asrcH != srcH) { /* reverse before application */
            CImage( x)-> stretch( x, srcW, srcH);
            srcW = asrcW;
            srcH = asrcH;
            if ( PImage(x)-> w != asrcW || PImage(x)-> h != asrcH) {
               Object_destroy( x);
               return true;
            }
         }

         if (( type & imBPP) < 8) type = imbpp8;

         profile = newHV();
         pset_i( type,        type);
         pset_i( width,       asrcW);
         pset_i( height,      asrcH);
         pset_i( conversion,  PImage( src)-> conversion);
         dx = Object_create( "Prima::Image", profile);
         sv_free((SV*)profile);
         if ( !dx) {
            Object_destroy( x);
            return false;
         }
         if ( PImage( dx)-> palSize > 0) {
            PImage( dx)-> palSize = PImage( x)-> palSize;
            memcpy( PImage( dx)-> palette, PImage( x)-> palette, 768);
         }
         memset( PImage( dx)-> data, 0, PImage( dx)-> dataSize);

         if ( srcX < 0) dsx = asrcW - dsw;
         if ( srcY < 0) dsy = asrcH - dsh;
         img_put( dx, x, dsx, dsy, 0, 0, dsw, dsh, dsw, dsh, ropCopyPut);
         Object_destroy( x);
         x = dx;
      }

      src = x;
      newObject = true;
      srcX = srcY = 0;
   } 

   if ( srcW != dstW || srcH != dstH) {
      /* stretch & reverse */
      if ( !newObject) {
         src = CImage( src)-> dup( src);
         if ( !src) goto EXIT;
         newObject = true;
      }
      if ( srcW != asrcW) { 
         dstW = -dstW;
         srcW = asrcW;
      }
      if ( srcH != asrcH) { 
         dstH = -dstH;
         srcH = asrcH;
      }
      CImage(src)-> stretch( src, dstW, dstH);
      if ( PImage(src)-> w != dstW || PImage(src)-> h != dstH) goto EXIT;
      dstW = abs( dstW);
      dstH = abs( dstH);
   }

NOSCALE:   

   if (( PImage( dest)-> type & imBPP) < 8) {
      PImage i = ( PImage) dest;
      int type = i-> type;
      if (rop != ropCopyPut || i-> conversion == ictNone) { 
         Handle b8 = i-> self-> dup( dest);
         PImage j  = ( PImage) b8;
         int mask  = (1 << (type & imBPP)) - 1;
         int sz;
         Byte *dj, *di;
         Byte colorref[256];
         j-> self-> reset( b8, imbpp8, nil, 0);
         sz = j-> dataSize;
         dj = j-> data;
         /* change 0/1 to 0x000/0xfff for correct masking */
         while ( sz--) {
            if ( *dj == mask) *dj = 0xff;
            dj++;
         }
         img_put( b8, src, dstX, dstY, 0, 0, dstW, dstH, PImage(src)-> w, PImage(src)-> h, rop);
         for ( sz = 0; sz < 256; sz++) colorref[sz] = ( sz > mask) ? mask : sz;
         dj = j-> data;
         di = i-> data;

         for ( sz = 0; sz < i-> h; sz++, dj += j-> lineSize, di += i-> lineSize) {
            if (( type & imBPP) == 1) 
               bc_byte_mono_cr( dj, di, i-> w, colorref);
            else
               bc_byte_nibble_cr( dj, di, i-> w, colorref);
         }
         Object_destroy( b8);
      } else {
         int conv = i-> conversion;
         i-> conversion = PImage( src)-> conversion;
         i-> self-> reset( dest, imbpp8, nil, 0);
         img_put( dest, src, dstX, dstY, 0, 0, dstW, dstH, PImage(src)-> w, PImage(src)-> h, rop);
         i-> self-> reset( dest, type, nil, 0);
         i-> conversion = conv;
      }
      goto EXIT;
   } 

   if ( PImage( dest)-> type != PImage( src)-> type) {
      int type = PImage( src)-> type & imBPP;
      int mask = (1 << type) - 1;
      /* equalize type */
      if ( !newObject) {
         src = CImage( src)-> dup( src);
         if ( !src) goto EXIT;
         newObject = true;
      }
      CImage( src)-> reset( src, PImage( dest)-> type, nil, 0);
      if ( type < 8 && rop != ropCopyPut) { 
         /* change 0/1 to 0x000/0xfff for correct masking */
         int sz   = PImage( src)-> dataSize;
         Byte * d = PImage( src)-> data;
         while ( sz--) {
            if ( *d == mask) *d = 0xff;
            d++;
         }
         memset( PImage( src)-> palette + 255, 0xff, sizeof(RGBColor));
      }
   }

   if ( PImage( dest)-> type == imbpp8) {
      /* equalize palette */
      Byte colorref[256], *s;
      int sz, i = PImage( src)-> dataSize;
      if ( !newObject) {
         src = CImage( src)-> dup( src);
         if ( !src) goto EXIT;
         newObject = true;
      }
      cm_fill_colorref( 
         PImage( src)-> palette, PImage( src)-> palSize,
         PImage( dest)-> palette, PImage( dest)-> palSize,
         colorref);
      s = PImage( src)-> data;
      /* identity transform for padded ( 1->xfff, see above ) pixels */
      for ( sz = PImage( src)-> palSize; sz < 256; sz++)
         colorref[sz] = sz;
      while ( i--) {
         *s = colorref[ *s];
         s++;
      }
   }

   if ( dstX < 0 || dstY < 0 || dstX + dstW >= dstSz. x || dstY + dstH >= dstSz. y) {
      /* adjust destination rectangle */
      if ( dstX < 0) {
         dstW += dstX;
         srcX -= dstX;
         dstX = 0;
      }
      if ( dstY < 0) {
         dstH += dstY;
         srcY -= dstY;
         dstY = 0;
      }
      if ( dstX + dstW > dstSz. x)
         dstW = dstSz. x - dstX;
      if ( dstY + dstH > dstSz. y) 
         dstH = dstSz. y - dstY;
   }

   /* checks done, do put_image */
   {
      int  y, dyd, dys, count, pix;
      Byte *dptr, *sptr;
      PBitBltProc proc = find_blt_proc(rop);

      pix = ( PImage( dest)-> type & imBPP ) / 8;
      dyd = PImage( dest)-> lineSize;
      dys = PImage( src)-> lineSize;
      sptr = PImage( src )-> data + dys * srcY + pix * srcX;
      dptr = PImage( dest)-> data + dyd * dstY + pix * dstX;
      count = dstW * pix;

      if ( proc == bitblt_copy && dest == src) /* incredible */
         proc = bitblt_move;
      
      for ( y = 0; y < dstH; y++, sptr += dys, dptr += dyd) 
         proc( sptr, dptr, count);
   }

EXIT:
   if ( newObject) Object_destroy( src);

   return true;
}

void 
img_bar( Handle dest, int x, int y, int w, int h, int rop, void * color)
{
   PImage i     = (PImage) dest;
   Byte * data  = i-> data;
   int dataSize = i-> dataSize; 
   int lineSize = i-> lineSize; 
   int palSize  = i-> palSize; 
   int type     = i-> type;
   int pixSize  = (type & imBPP) / 8;
   PBitBltProc proc;
#define BLT_BUFSIZE 1024
   Byte blt_buffer[BLT_BUFSIZE];
   int j, k, offset, blt_bytes, blt_step;
   Byte filler, lmask, rmask, *p, *q;
  
   if ( x < 0 ) {
      w += x;
      x = 0;
   }
   if ( y < 0 ) {
      h += y;
      y = 0;
   }
   if ( x + w > i->w ) w = i->w - x;
   if ( y + h > i->h ) h = i->h - y;
   if ( w <= 0 || h <= 0 ) return;

   switch ( type & imBPP) {
   case imbpp1:
      filler = (*((Byte*)color)) ? 255 : 0;
      blt_bytes = (( x + w - 1) >> 3) - (x >> 3) + 1;
      blt_step = (blt_bytes > BLT_BUFSIZE) ? BLT_BUFSIZE : blt_bytes;
      memset( blt_buffer, filler, blt_step);
      lmask = ( x & 7 ) ? 255 << ( 8 - x & 7) : 0;
      rmask = (( x + w) & 7 ) ? 255 >> ((x + w) & 7) : 0;
      offset = x >> 3;
      break;
   case imbpp4:
      filler = (*((Byte*)color)) * 17;
      blt_bytes = (( x + w - 1) >> 1) - (x >> 1) + 1;
      blt_step = (blt_bytes > BLT_BUFSIZE) ? BLT_BUFSIZE : blt_bytes;
      memset( blt_buffer, filler, blt_step);
      lmask = ( x & 1 )       ? 0xf0 : 0;
      rmask = (( x + w) & 1 ) ? 0x0f : 0;
      offset = x >> 1;
      break;
   case imbpp8:
      filler = (*((Byte*)color));
      blt_bytes = w;
      blt_step = (blt_bytes > BLT_BUFSIZE) ? BLT_BUFSIZE : blt_bytes;
      memset( blt_buffer, filler, blt_step);
      lmask = rmask = 0;
      offset = x;
      break;
   default:
      blt_bytes = w * pixSize;
      blt_step = (blt_bytes > BLT_BUFSIZE) ? BLT_BUFSIZE / pixSize : w;
      for ( j = 0, p = blt_buffer; j < blt_step; j ++ )
         for ( k = 0, q = (Byte*) color; k < pixSize; k++ )
            *(p++) = *(q++);
      blt_step *= pixSize;
      lmask = rmask = 0;
      offset = x * pixSize;
   }

   data += lineSize * y + offset;
   proc = find_blt_proc(rop);

#define DEBUG_ 1
#ifdef DEBUG
   warn("%d/%d, blt_bytes:%d, blt_step:%d, lmask:%02x, rmask:%02x, buf:%02x%02x%02x%02x\n", 
   	x, w, blt_bytes, blt_step, lmask, rmask, blt_buffer[0], blt_buffer[1], blt_buffer[2], blt_buffer[3]);
#endif	

   for ( j = 0; j < h; j++) {
      int bytes = blt_bytes;
      Byte lsave = *data, rsave = data[blt_bytes - 1], *p = data;
      while ( bytes > 0 ) {
         proc( blt_buffer, p, ( bytes > blt_step ) ? blt_step : bytes );
         bytes -= blt_step;
         p += blt_step;
      }
      if ( lmask ) *data = (lsave & lmask) | (*data & ~lmask);
      if ( rmask ) data[blt_bytes-1] = (rsave & rmask) | (data[blt_bytes-1] & ~rmask);
      data += lineSize;
   }
}

static void
fill_alpha_buf( Byte * dst, Byte * src, int width, int bpp)
{
   register int x = width;
   if ( bpp == 3 ) {
      while (x-- > 0) {
         register Byte a = *src++;
         *dst++ = a;
         *dst++ = a;
         *dst++ = a;
      }
   } else
      memcpy( dst, src, width * bpp);
}

#define dBLEND_FUNC(name) void name( const Byte * src, const Byte * src_a, Byte * dst, const Byte * dst_a, int bytes )

typedef dBLEND_FUNC(BlendFunc);

/* sss + (ddd * (255 - as)) / 255 */
static dBLEND_FUNC(blend_src_over)
{
   while ( bytes-- > 0 ) {
      register int32_t s = 
            ((int32_t)(*src++) << 8 ) +  
	    ((int32_t)(*dst) << 8) * (255 - *src_a++) / 255 
	    + 127;
      s >>= 8;
      *dst++ = ( s > 255 ) ? 255 : s;
   }
}

/* (sss * (255 - ad) + ddd * (255 - as)) / 255 */
static dBLEND_FUNC(blend_xor)
{
   while ( bytes-- > 0 ) {
      register int32_t s = (
            ((int32_t)(*src++) << 8) * (255 - *dst_a++) + 
	    ((int32_t)(*dst)   << 8) * (255 - *src_a++)
	 ) / 255 + 127;
      s >>= 8;
      *dst++ = ( s > 255 ) ? 255 : s;
   }
}

/* sss * (255 - ad) / 255 + ddd */
static dBLEND_FUNC(blend_dst_over)
{
   while ( bytes-- > 0 ) {
      register int32_t s = 
            ((int32_t)(*dst) << 8 ) +  
	    ((int32_t)(*src++) << 8) * (255 - *dst_a++) / 255 
	    + 127;
      s >>= 8;
      *dst++ = ( s > 255 ) ? 255 : s;
   }
}

/* ddd */
static dBLEND_FUNC(blend_dst_copy)
{
}

/* 0 */
static dBLEND_FUNC(blend_clear)
{
   memset( dst, 0, bytes);
}

/* sss * ad / 255 */
static dBLEND_FUNC(blend_src_in)
{
   while ( bytes-- > 0 ) {
      register int32_t s = (((int32_t)(*src++) << 8) * *dst_a++) / 255 + 127;
      s >>= 8;
      *dst++ = ( s > 255 ) ? 255 : s;
   }
}

/* ddd * as / 255 */
static dBLEND_FUNC(blend_dst_in)
{
   while ( bytes-- > 0 ) { 
      register int32_t d = (((int32_t)(*dst) << 8) * *src_a++) / 255 + 127;
      d >>= 8;
      *dst++ = ( d > 255 ) ? 255 : d;
   }
}

/* sss * (255 - ad) / 255 */
static dBLEND_FUNC(blend_src_out)
{
   while ( bytes-- > 0 ) {
      register int32_t s = (((int32_t)(*src++) << 8) * ( 255 - *dst_a++)) / 255 + 127;
      s >>= 8;
      *dst++ = ( s > 255 ) ? 255 : s;
   }
}

/* ddd * (255 - as) / 255 */
static dBLEND_FUNC(blend_dst_out)
{
   while ( bytes-- > 0 ) {
      register int32_t d = (((int32_t)(*dst) << 8) * ( 255 - *src_a++)) / 255 + 127;
      d >>= 8;
      *dst++ = ( d > 255 ) ? 255 : d;
   }
}

/* (sss * ad + ddd * (255 - as)) / 255 */
static dBLEND_FUNC(blend_src_atop)
{
   while ( bytes-- > 0 ) {
      register int32_t s = (
         ((int32_t)(*src++) << 8) * *dst_a++ + 
	 ((int32_t)(*dst) << 8) * (255 - *src_a++)
      ) / 255 + 127;
      s >>= 8;
      *dst++ = ( s > 255 ) ? 255 : s;
   }
}

/* (sss * (255 - ad) + ddd * as) / 255 */
static dBLEND_FUNC(blend_dst_atop)
{
   while ( bytes-- > 0 ) {
      register int32_t s = (
         ((int32_t)(*src++) << 8) * ( 255 - *dst_a++) + 
	 ((int32_t)(*dst) << 8) * *src_a++
      ) / 255 + 127;
      s >>= 8;
      *dst++ = ( s > 255 ) ? 255 : s;
   }
}

/* sss */
static dBLEND_FUNC(blend_src_copy)
{
   memcpy( dst, src, bytes);
}

static BlendFunc* blend_functions[] = {
   blend_src_over,
   blend_xor,
   blend_dst_over,
   blend_src_copy,
   blend_dst_copy,
   blend_clear,
   blend_src_in,
   blend_dst_in,
   blend_src_out,
   blend_dst_out,
   blend_src_atop,
   blend_dst_atop
};

/* 
   This is basically a lightweight pixman_image_composite() .
   Converts images to either 8 or 24 bits before processing
*/
static Bool 
img_put_alpha( Handle dest, Handle src, int dstX, int dstY, int srcX, int srcY, int dstW, int dstH, int srcW, int srcH, int rop)
{
   int bpp, bytes, sls, dls, mls, als, x, y, px;
   Byte *s, *d, *m, *a; 
   unsigned int src_alpha = 0, dst_alpha = 0;
   Bool use_src_alpha = false, use_dst_alpha = false;
   Byte *asbuf, *adbuf;
   BlendFunc * blend_func;

   /* differentiate between per-pixel alpha and a global value */
   if ( rop & ropSrcAlpha ) {
      use_src_alpha = true;
      src_alpha = (rop >> ropSrcAlphaShift) & 0xff;
   }
   if ( rop & ropDstAlpha ) {
      use_dst_alpha = true;
      dst_alpha = (rop >> ropDstAlphaShift) & 0xff;
   }
   rop &= ropPorterDuffMask;
   if ( rop > ropDstAtop || rop < 0 ) return false;

   /* align types and geometry - can only operate over imByte and imRGB */
   bpp = (( PImage(src)->type & imGrayScale) && ( PImage(dest)->type & imGrayScale)) ? imByte : imRGB;

   /* adjust rectangles */
   if ( dstX < 0 || dstY < 0 || dstX + dstW >= PImage(dest)-> w || dstY + dstH >= PImage(dest)-> h) {
      if ( dstX < 0) {
         dstW += dstX;
         srcX -= dstX;
         dstX = 0;
      }
      if ( dstY < 0) {
         dstH += dstY;
         srcY -= dstY;
         dstY = 0;
      }
      if ( dstX + dstW > PImage(dest)-> w)
         dstW = PImage(dest)-> w - dstX;
      if ( dstY + dstH > PImage(dest)-> h) 
         dstH = PImage(dest)-> h - dstY;
   }
   if ( srcX + srcW > PImage(src)-> w)
      srcW = PImage(src)-> w - srcX;
   if ( srcY + srcH > PImage(src)-> h)
      srcH = PImage(src)-> h - srcY;
   if ( srcH <= 0 || srcW <= 0 )
      return false;

   /* adjust source type */
   if (PImage(src)-> type != bpp || srcW != dstW || srcH != dstH ) {
      Bool ok;
      Handle dup;
     
      if ( srcW != PImage(src)-> w || srcH != PImage(src)-> h)
         dup = CImage(src)-> extract( src, srcX, srcY, srcW, srcH );
      else
         dup = CImage(src)-> dup(src);

      if ( srcW != dstW || srcH != dstH )
         CImage(dup)->stretch( dup, dstW, dstH );
      if ( PImage( dup )-> type != bpp )
         CImage(dup)-> set_type( dup, bpp);

      ok = img_put_alpha( dest, dup, dstX, dstY, 0, 0, dstW, dstH, dstW, srcH, rop);

      Object_destroy(dup);
      return ok;
   }

   /* adjust destination type */
   if (PImage(dest)-> type != bpp || ( kind_of( dest, CIcon) && PIcon(dest)->maskType != imbpp8 )) {
      Bool ok;
      Bool icon = kind_of(dest, CIcon);
      int type = PImage(dest)->type;
      int mask = icon ? PIcon(dest)->maskType : 0;

      if ( type != bpp )
         CIcon(dest)-> set_type( dest, bpp );
      if ( icon && mask != imbpp8 )
         CIcon(dest)-> set_maskType( dest, imbpp8 );
      ok = img_put_alpha( dest, src, dstX, dstY, srcX, srcY, dstW, dstH, srcW, srcH, rop);
      if ( PImage(dest)-> options. optPreserveType ) {
         if ( type != bpp )
            CImage(dest)-> set_type( dest, type );
         if ( icon && mask != imbpp8 )
            CIcon(dest)-> set_maskType( dest, mask );
      }
      return ok;
   }
   
   /* assign pointers */
   if ( srcW != dstW || srcH != dstH || 
       PImage(src)->type != PImage(dest)->type || PImage(src)-> type != bpp)
       croak("panic: assert failed for img_put_alpha: %s", "types and geometry");
   
   bpp = ( bpp == imByte ) ? 1 : 3;
   sls = PImage(src)-> lineSize;
   dls = PImage(dest)-> lineSize;
   s = PImage(src )-> data + srcY * sls + srcX * bpp;
   d = PImage(dest)-> data + dstY * dls + dstX * bpp;

   if ( kind_of(src, CIcon)) {
      mls = PIcon(src)-> maskLine;
      m   = PIcon(src)-> mask + srcY * mls + srcX;
      if ( PIcon(src)-> maskType != imbpp8)
        croak("panic: assert failed for img_put_alpha: %s", "src mask type");
      use_src_alpha = false;
   } else {
      m   = NULL;
      mls = 0;
   }

   if ( kind_of(dest, CIcon)) {
      als = PIcon(dest)-> maskLine;
      a   = PIcon(dest)-> mask + dstY * als + dstX;
      if ( PIcon(dest)-> maskType != imbpp8)
        croak("panic: assert failed for img_put_alpha: %s", "dst mask type");
      use_dst_alpha = false;
   } else {
      a   = NULL;
      als = 0;
   }

   if ( !use_src_alpha && !m) {
      use_src_alpha = true;
      src_alpha = 0xff;
   }
   if ( !use_dst_alpha && !a) {
      use_dst_alpha = true;
      dst_alpha = 0xff;
   }

   /* make buffers for alpha */
   bytes = dstW * bpp;
   if ( !(asbuf = malloc(bytes * (use_src_alpha ? 1 : OMP_MAX_THREADS)))) {
      warn("not enough memory");
      return false;
   }
   if ( !(adbuf = malloc(bytes * (use_dst_alpha ? 1 : OMP_MAX_THREADS)))) {
      free(asbuf);
      warn("not enough memory");
      return false;
   }

   if ( use_src_alpha )
      for ( x = 0; x < bytes; x++) asbuf[x] = src_alpha;
   if ( use_dst_alpha )
      for ( x = 0; x < bytes; x++) adbuf[x] = dst_alpha;

   /* select function */
   blend_func = blend_functions[rop];

   /* blend */
#ifdef HAVE_OPENMP
#pragma omp parallel for
#endif
   for ( y = 0; y < dstH; y++) {
      Byte *asbuf_ptr, *adbuf_ptr, *s_ptr, *m_ptr, *d_ptr, *a_ptr;

      s_ptr = s + sls * y;
      d_ptr = d + dls * y;
      if (m) m_ptr = m + mls * y;
      if (a) a_ptr = a + als * y;

      if ( !use_src_alpha ) {
         asbuf_ptr = asbuf + bytes * OMP_THREAD_NUM;
         fill_alpha_buf( asbuf_ptr, m_ptr, dstW, bpp);
      } else
         asbuf_ptr = asbuf;

      if ( !use_dst_alpha ) {
         adbuf_ptr = adbuf + bytes * OMP_THREAD_NUM;
         fill_alpha_buf( adbuf, a_ptr, dstW, bpp);
      } else
         adbuf_ptr = adbuf;
      
      blend_func( s_ptr, asbuf_ptr, d_ptr, adbuf_ptr, bytes);
      if (a) {
         if ( use_src_alpha )
            blend_func( asbuf, asbuf, a_ptr, a_ptr, dstW);
	 else
            blend_func( m_ptr, m_ptr, a_ptr, a_ptr, dstW);
      }
   }

   /* cleanup */
   free(adbuf);
   free(asbuf);

   return true;
}

/* alpha stuff */
void
img_premultiply_alpha_constant( Handle self, int alpha)
{
    Byte * data;
    int i, j, pixels;
    if ( PImage(self)-> type == imByte ) {
       pixels = 1;
    } else if ( PImage(self)-> type == imRGB ) {
       pixels = 3;
    } else {
       croak("Not implemented");
    }

    data = PImage(self)-> data;
    for ( i = 0; i < PImage(self)-> h; i++) {
       register Byte *d = data, k;
       for ( j = 0; j < PImage(self)-> w; j++ ) {
          for ( k = 0; k < pixels; k++, d++)
	     *d = (alpha * *d) / 255.0 + .5;
       }
       data += PImage(self)-> lineSize;
    }
}

void img_premultiply_alpha_map( Handle self, Handle alpha)
{
    Byte * data, * mask;
    int i, j, pixels;
    if ( PImage(self)-> type == imByte ) {
       pixels = 1;
    } else if ( PImage(self)-> type == imRGB ) {
       pixels = 3;
    } else {
       croak("Not implemented");
    }

    if ( PImage(alpha)-> type != imByte )
       croak("Not implemented");

    data = PImage(self)-> data;
    mask = PImage(alpha)-> data;
    for ( i = 0; i < PImage(self)-> h; i++) {
       int j;
       register Byte *d = data, *m = mask, k;
       for ( j = 0; j < PImage(self)-> w; j++ ) {
          register uint16_t alpha = *m++;
          for ( k = 0; k < pixels; k++, d++)
	     *d = (alpha * *d) / 255.0 + .5;
       }
       data += PImage(self)-> lineSize;
       mask += PImage(alpha)-> lineSize;
    }
}

#ifdef __cplusplus
}
#endif

