import java.applet.*; 
import java.awt.*; 
import java.awt.image.*; 
import java.awt.event.*; 
import java.io.*; 
import java.net.*; 
import java.text.*; 
import java.util.*; 
import java.util.zip.*; 

public class alphablit2 extends BApplet {
class AlphaImg {
  public static final int   MODE_BLEND      = 1;
  public static final int   MODE_ADD        = 2;
  public static final int   MODE_SUB        = 4;
  public static final int   MODE_DISSOLVE   = 8;
  public static final int   MODE_MIN        = 16;
  public static final int   MODE_MAX        = 32;
  
  BImage  img;
  int[]   mask;
  int     area;
    
  AlphaImg(String basefile, String maskfile) {
    this.load(basefile,maskfile);
  }

  // load a new image and alpha channel from file/url
  public void load(String basefile, String maskfile) {
    img=loadImage(basefile);
    BImage alpha=loadImage(maskfile);
    if (img.width!=alpha.width || img.height!=alpha.height) throw new IllegalArgumentException("AlphaImg: alpha image needs to be the same size as main image");
    area=img.width*img.height;
    this.setAlpha(alpha.pixels);
  }
  
  // overwrite current alpha channel
  // if the array contains RGB data, only the blue channel will be used as alpha data
  public void setAlpha(int[] alpha) {
    if (alpha.length==area) {
      mask=new int[area];
      for(int i=0; i<area; i++) mask[i]=alpha[i]&0xff;
    }
  }
  
  // return dimensions of the image
  public int getWidth() { return img.width; }
  public int getHeight() { return img.height; }
  
  // generic clipping
  public static int low(int a, int b) { return (a<b) ? a : b; }
  public static int high(int a, int b) { return (a>b) ? a : b; }

  // generic linear interpolation
  public static int mix(int a,int b, int f) { return a+(((b-a)*f)>>8); }

  //
  // implementations of various blending modes below...
  // now using fixed point integer maths to avoid mutliplication with floats -> much faster!!!
  //
  // blend 2 colours
  public static int blend(int a, int b, int f) {
    return mix(a>>16,b>>16,f)<<16 | mix(a>>8&0xff,b>>8&0xff,f)<<8 | mix(a&0xff,b&0xff,f);
  }

  // additive blend with clipping
  public static int addPin(int a, int b, int f) {
    return low((a>>16&0xff) + (((b>>16&0xff)*f)>>8),0xff)<<16 |
           low((a>>8&0xff)  + (((b>>8 &0xff)*f)>>8),0xff)<<8 |
           low((a&0xff)     + (((b    &0xff)*f)>>8),0xff);
  }

  // substractive blend with clipping
  public static int subPin(int a,int b, int f) {
    return high((a>>16&0xff) - (((b>>16&0xff)*f)>>8),0)<<16 |
           high((a>>8&0xff)  - (((b>>8&0xff) *f)>>8),0)<<8 |
           high((a&0xff)     - (((b&0xff)    *f)>>8),0);
  }

  // dissolve blending, chance for colour to appear based on the alpha value
  public static int dissolve(int a,int b, int f) {
    return (Math.random()*0xff<=f) ? blend(a,b,f) : a;
  }
  
  // only returns lightest of the 2 colours
  public static int lightest(int a, int b, int f) {
    return high(a>>16&0xff, ((b>>16&0xff)*f )>>8)<<16 |
           high(a>>8&0xff,  ((b>>8 &0xff)*f )>>8)<<8 |
           high(a&0xff,     ((b&0xff    )*f )>>8);
  }
  
  // only returns darkest colour
  public static int darkest(int a, int b, int f) {
    return mix(a>>16&0xff, low(a>>16&0xff, ((b>>16&0xff)*f)>>8),f)<<16 |
           mix(a>>8&0xff,  low(a>>8&0xff,  ((b>>8 &0xff)*f)>>8),f)<<8 |
           mix(a&0xff,     low(a&0xff,     ((b    &0xff)*f)>>8),f);
  }
  
  // main display method
  public void display(int x, int y, boolean centered, int mode) {
    if (centered) { x-=img.width/2; y-=img.height/2; }
    if (-x>=img.width || -y>=img.height) return;
    int tw=(x<0) ? x+img.width : min(width-x,img.width);
    int th=(y<0) ? y+img.height : min(height-y, img.height);
    int offset1=((x<0) ? -x : 0)+((y<0) ? -y : 0)*img.width;
    int offset2=((x<0) ? 0 : x)+((y<0) ? 0 : y)*width;
    
    int twidth1=img.width-tw;
    int twidth2=width-tw;
    
    int yy=-1;
    switch(mode) {
    
      case MODE_BLEND:
        while(++yy<th) {
          int xx=-1;
          while(++xx<tw) {
            pixels[offset2] = blend(pixels[offset2],img.pixels[offset1],mask[offset1]);
            offset1++; offset2++;
          }
          offset1+=twidth1; offset2+=twidth2;
        }
        break;
        
      case MODE_ADD:
        while(++yy<th) {
          int xx=-1;
          while(++xx<tw) {
            pixels[offset2] = addPin(pixels[offset2],img.pixels[offset1],mask[offset1]);
            offset1++; offset2++;
          }
          offset1+=twidth1; offset2+=twidth2;
        }
        break;
        
       case MODE_SUB:
        while(++yy<th) {
          int xx=-1;
          while(++xx<tw) {
            pixels[offset2] = subPin(pixels[offset2],img.pixels[offset1],mask[offset1]);
            offset1++; offset2++;
          }
          offset1+=twidth1; offset2+=twidth2;
        }
        break;
      
       case MODE_DISSOLVE:
        while(++yy<th) {
          int xx=-1;
          while(++xx<tw) {
            pixels[offset2] = dissolve(pixels[offset2],img.pixels[offset1],mask[offset1]);
            offset1++; offset2++;
          }
          offset1+=twidth1; offset2+=twidth2;
        }
        break;
      
      case MODE_MIN:
        while(++yy<th) {
          int xx=-1;
          while(++xx<tw) {
            pixels[offset2] = darkest(pixels[offset2],img.pixels[offset1],mask[offset1]);
            offset1++; offset2++;
          }
          offset1+=twidth1; offset2+=twidth2;
        }
        break;
      
      case MODE_MAX:
        while(++yy<th) {
          int xx=-1;
          while(++xx<tw) {
            pixels[offset2] = lightest(pixels[offset2],img.pixels[offset1],mask[offset1]);
            offset1++; offset2++;
          }
          offset1+=twidth1; offset2+=twidth2;
        }
        break;
        
      default:
        throw new IllegalArgumentException("AlphaImg: unknown blend mode: "+mode);
    }
  }
  
}

AlphaImg  testImg;
int       currMode;

void setup() {
  size(256,256);
  testImg=new AlphaImg("test.jpg","mask.jpg");
  currMode=AlphaImg.MODE_BLEND;
}

void loop() {
  background((mouseX+mouseY)/2);
  testImg.display(width/2,height/2,true,currMode);
  testImg.display(mouseX,mouseY,true,currMode);
}

void mouseReleased() {
  currMode*=2;
  if (currMode==64) currMode=AlphaImg.MODE_BLEND;
}

}