import java.lang.Math;

import java.awt.*;
import java.awt.image.*;

/** Example of use.
 * // don't have to do it this way but we can
 * // one for the class -- to construct by first to need it
 * protected static MultiFilter myFilter = null;
 * // first need so construct it now
 * if (myFilter == null) myFilter = new MultiFilter ();
 *
 * // now use myFilter to derive Gray image from given image
 * myFilter.setFilterMode  (MultiFilter.GRAY);
 * Image grayImage = myFilter.filterImage (fromImage, aComponent);
 *
 * // now derive an image where black becomes transparent
 * myFilter.setTransparentColor (Color.black);
 * myFilter.setFilterMode  (MultiFilter.TRANSPARENT);
 * Image transpImage = myFilter.filterImage (fromImage, aComponent);
 */

public class MultiFilter extends RGBImageFilter {

/** Filter modes, one must be set */

  public static final int
    DARK = 1,
    LIGHT = 2,
    GRAY = 3,
    TRANSPARENT = 4,
    ALPHA = 5;

  protected int filterMode;

  protected int alpha_scale = 122;

/* R G B components of special transparent color for testing,
 * default is black (0, 0, 0) */

  protected int tRed = 0, tGreen = 0, tBlue = 0;

  protected DirectColorModel cm = 
     (DirectColorModel)ColorModel.getRGBdefault ();

/* Constructor declares we canFilterIndexColorModel,
 * rather than each pixel location.
 * This is much faster.
 * See public int filterRGB (int x, int y, int rgb) method below.  */

/** Constructor.
 * @param filterMode, one of: DARK, LIGHT, GRAY, TRANSPARENT, ALPHA */

  public MultiFilter (int filterMode) {
    canFilterIndexColorModel = true;
    this.filterMode = filterMode;
  }

/** Constructor with default mode */

  public MultiFilter () { this (GRAY); }

/** Set (change) the mode.
 * @param filterMode, one of: DARK, LIGHT, GRAY, TRANSPARENT, ALPHA */

  public void setFilterMode (int filterMode) { this.filterMode = filterMode; }

/** Set (change) the alpha for ALPHA mode,
 * @param alpha, from 0 (fully transparent) through 255 (opaque) */

  public void setAlphaScale (int av) { this.alpha_scale = av; }

/** Set the special transparent Color for TRANSPARENT mode.
 * @param transp, the transparent Color,
 * default is black (0, 0, 0) */

  public void setTransparentColor (Color transp) {
    if (null == transp)
        return;

    tRed = transp.getRed ();
    tGreen = transp.getGreen ();
    tBlue = transp.getBlue ();
  }

/** Apply the filter to an image.
 * @param fromImage, the image to work from,
 * @param comp, any AWT Component,
 * @return, a filtered image. */

  public Image filterImage (Image fromImage, Component comp) {
    Image outImage;
    FilteredImageSource fis = 
        new FilteredImageSource (fromImage.getSource (), this);
        
    waitForImage (comp, outImage = comp.createImage (fis));
    return outImage;
  }

/** Filter a color according to the chosen mode.
 * Application code would not normally call this method.
 * It would call filterImage (),
 * which in turn does fis = new FilteredImageSource (),
 * and createImage (fis),
 * which calls here.
 *
 * Because we have declared canFilterIndexColorModel = true;
 * which will be tested by methods in our super class,
 * this method is called only once per color,
 * instead of once per pixel,
 * and we ignore x and y. */

   public int filterRGB (int x, int y, int rgb) {
     switch (filterMode) {
       case DARK: return darkFilter (x, y, rgb);
       case LIGHT: return lightFilter (x, y, rgb);
       case GRAY: return grayFilter (x, y, rgb);
       case TRANSPARENT: return transparentFilter (x, y, rgb);
       case ALPHA: return alphaFilter (x, y, rgb);
     }
     return 0;
   }

/* Darken by reducing each color value */

  protected int darkFilter (int x, int y, int rgb) {

    int    alpha = cm.getAlpha (rgb);
    int    red   = (cm.getRed   (rgb) * 3) / 4;
    int    green = (cm.getGreen (rgb) * 3) / 4;
    int    blue  = (cm.getBlue  (rgb) * 3) / 4;

    alpha = alpha << 24;
    red   = red   << 16;
    green = green << 8;

    return alpha | red | green | blue;
  }

/* Lighten by increasing each color value if possible */

  protected int lightFilter (int x, int y, int rgb) {

    int    alpha = cm.getAlpha (rgb);
    int    red   = Math.min (((cm.getRed   (rgb) * 4) / 3), 255);
    int    green = Math.min (((cm.getGreen (rgb) * 4) / 3), 255);
    int    blue  = Math.min (((cm.getBlue  (rgb) * 4) / 3), 255);

    alpha = alpha << 24;
    red   = red   << 16;
    green = green << 8;

    return alpha | red | green | blue;
  }

/* Average color values for shades of gray */

  protected int grayFilter (int x, int y, int rgb) {

    int    alpha = cm.getAlpha (rgb);
    int    red   = cm.getRed   (rgb);
    int    green = cm.getGreen (rgb);
    int    blue  = cm.getBlue  (rgb);

/* Double weight to green component (somewhat like UYV model)
 * and alot of gray thrown in to fade even the shadings.
 * You may want less fade. */

    int  mixed = (100 + red + green + green + blue) / 5;

    red   = blue = green = mixed;

    alpha = alpha << 24;
    red   = red   << 16;
    green = green << 8;

    return alpha | red | green | blue;
  }

/** Change a particular color to transparent,
 * leave others unchanged.
 * Note that 0 is transparent rather than black,
 * because of the 0 alpha value it implies. */

  protected int transparentFilter (int x, int y, int rgb) {

    int    alpha = cm.getAlpha (rgb);
    int    fRed   = cm.getRed   (rgb);
    int    fGreen = cm.getGreen (rgb);
    int    fBlue  = cm.getBlue  (rgb);

    if ((tRed == fRed) && (tGreen == fGreen) && (tBlue == fBlue))
   //if (nearly (tRed, fRed) && nearly (tGreen, fGreen) && nearly (tBlue, fBlue))
        return 0;

     else
        return rgb;
  }

/* Make all colors partially transparent by changing their alpha */

  protected int alphaFilter (int x, int y, int rgb) {

    int    fAlpha = (cm.getAlpha (rgb) * alpha_scale) / 255;
    int    fRed   = cm.getRed   (rgb);
    int    fGreen = cm.getGreen (rgb);
    int    fBlue  = cm.getBlue  (rgb);

    if ((tRed == fRed) && (tGreen == fGreen) && (tBlue == fBlue))
   //if (nearly (tRed, fRed) && nearly (tGreen, fGreen) && nearly (tBlue, fBlue))
        return 0;

    fAlpha = fAlpha << 24;
    fRed   = fRed   << 16;
    fGreen = fGreen << 8;

    return fAlpha | fRed | fGreen | fBlue;
  }

/* Chop a couple bits before comparing for equality so 4 == 7 */

  protected static boolean nearly (int a, int b) { return (a/4 == b/4); }

/* Wait until the Image is loaded -- or fails to */

  protected static void waitForImage (Component comp, Image theImage) {
    MediaTracker tracker = new MediaTracker (comp);
    try {
        tracker.addImage (theImage, 0);
        tracker.waitForID (0);
        if (tracker.isErrorID (0)) { System.out.println ("FAILED "+theImage); return; }
    }
    catch (Exception e) { System.out.println ("FAILED "+e); e.printStackTrace (); }
  }

}
/* <IMG SRC="/cgi-bin/counter">*/
