import ij.*; 

import java.awt.image.*; 


/** Implements an indexed color model that allows an upper and lower
    bound to be specified. Indices above upper bound are clamped to
    upper bound. Those below lower bound are clamped to lower
    bound. These clamped indices are then used to retrieve RGB values
    from RGB lookup tables. 

@author Patrick Kelly (phkelly@vision.ucsd.edu)

*/

public class ClampedColorModel extends ColorModel {
    /** Upper clamping value. */
    private int upper;
    /** Lower clamping value. */
    private int lower;    
    /** Lookup table to quickly map indexBit-bit values to
        indecies. */
    private int[] lut = null; 
    /** Look up tables. */
    private byte[] reds,greens,blues; 

    /** Number of elements in rgb lookup table. */
    private int size; 
    /** Number of bits in an index.  */
    private int indexBits; 

    private int mask; 

    /** Default number of bits in indices. */
    protected static final int DEFAULT_INDEX_BITS = 12; 

    private static final int BYTE_2_INT_MASK    = 0xff; 

    // --------------------------------------------------
    /** Create a ClampedColorModel whose indices have default number
	of bits.
	@param size number of values in rgb color maps
	@param r red color map with size entries
	@param g green color map with size entries
	@param b blue color map with size entries */
    public ClampedColorModel(int size,
			     byte[] r, byte[] g, byte[] b) {
	this(size,r,g,b,DEFAULT_INDEX_BITS); 
    }
    

    // --------------------------------------------------
    /** Create a ClampedColorModel with indices having specified
	number of bits.
	@param size number of values in rgb color maps
	@param r red color map with size entries
	@param g green color map with size entries
	@param b blue color map with size entries 
	@param indexBits number of bits in indices */
    public ClampedColorModel(int size, 
			     byte[] r, byte[] g, byte[] b,
			     int indexBits) {
	this(size,r,g,b,indexBits,0,(1<<indexBits)-1); // default lower, upper
    }


    // --------------------------------------------------
    /** Create a ClampedColorModel with indices having specified
	number of bits and specified upper and lower clamping bounds.
	@param size number of values in rgb color maps
	@param r red color map with size entries
	@param g green color map with size entries
	@param b blue color map with size entries 
	@param indexBits number of bits in indices 
	@param lower lower bound
	@param upper upper bound */
    public ClampedColorModel(int size, 
			     byte[] r, byte[] g, byte[] b, 
			     int indexBits,
			     int lower, int upper) {
	super(indexBits); // bits per input pixel?
	reds   = new byte[r.length];  System.arraycopy(r,0,reds,0,r.length); 
	greens = new byte[g.length];  System.arraycopy(g,0,greens,0,r.length);  
	blues  = new byte[b.length];  System.arraycopy(b,0,blues,0,r.length); 

	this.size = size; 
	this.indexBits = indexBits; 

	mask = (1<<indexBits)-1; 

	setLower(lower,false); 
	setUpper(upper,false); 

	computeLut(); 
    }


    // --------------------------------------------------
    /** Set lower bound.
	@param lower new lower bound
	@param recomp boolean specifying whether lookup tables values
	should be recomputed. Typically this flag should be true. */
    public void setLower(int lower, boolean recomp) {
	this.lower = lower; 

	if(recomp) 
	    computeLut(); 
    }


    // --------------------------------------------------
    /** Set upper bound.
	@param upper new upper bound
	@param recomp boolean specifying whether lookup tables values
	should be recomputed. Typically this flag should be true. */
    public void setUpper(int upper, boolean recomp) {
	this.upper = upper; 

	if(recomp)
	    computeLut(); 
    }

    
    // --------------------------------------------------
    /** Set both lower and upper bounds.
	@param lower new lower bound
	@param upper new upper bound
	@param recomp boolean specifying whether lookup tables values
	should be recomputed. Typically this flag be true. */
    public void setLowerAndUpper(int l, int u, boolean recomp) {
	setLower(l,false);
	setUpper(u,false); 

	if(recomp)
	    computeLut(); 
    }

    // --------------------------------------------------
    //
    // ColorModel interface.
    //
    // --------------------------------------------------
    public int getAlpha(int pixel) {
	return 0xff; 
    }


    // --------------------------------------------------
    public int getBlue(int pixel) {
	//pixel = clamp(pixel); 
	int value = blues[lut[pixel]] & BYTE_2_INT_MASK; 
	return value; 
    }


    // --------------------------------------------------
    public int getRed(int pixel) {
	//pixel = clamp(pixel);
	int value = reds[lut[pixel]] & BYTE_2_INT_MASK; 
	return value; 
    }


    // --------------------------------------------------
    public int getGreen(int pixel) {
	//pixel = clamp(pixel); 
	int value = greens[lut[pixel]] & BYTE_2_INT_MASK; 
	return value; 
    }


    // --------------------------------------------------
    public int getRGB(int pixel) {
	int value = getAlpha(pixel) << 24 | getRed(pixel) << 16 | 
	    getGreen(pixel) << 8 | getBlue(pixel); 

	return value; 
    }


    // --------------------------------------------------
    /** Clamp input index so that it is within bounds. Input indices
	below the lower value are set to lower. Those above upper are
	clamped to upper. */
    protected int clamp(int pixel) {
	if((pixel & mask) < lower) pixel = lower; // was 0
	if((pixel & mask) > upper) pixel = upper; // was mask; 

	return pixel; 
    }

    // --------------------------------------------------
    /** Compute values of internal lookup table that maps indexBits
	bit indices to values between 0 and size-1. This lookup table
	supports fast conversion of indices to color values. */
    protected void computeLut() {
	if(lut==null)
	    lut = new int [1<<indexBits]; 

	int min = 0; 
	int max = size-1; 

	float delta = upper - lower; 
	if(delta==0) delta = 1; 

	// Slope
	float m = (max - min)/delta; 
	// y-intercept
	float b = max - m * upper; 

	// Indecies between 0 and lower are mapped to min. Have to
	// explicitly min these entries out because we reuse lut
	// array.
	for(int n=0; n<=lower;++n)
	    lut[n] = min; 

	// Need to explicitly compute intermediate indices.
	for(int n=lower+1; n<upper; ++n) {
	    int index = n; 
	    index = (int)(m * index + b); 
	    //	    if(index < min) index = min; 
	    //	    if(index > max) index = max; 
	    lut[n] = index; 
	}

	// Everything above upper gets mapped to upper.
	for(int n=upper; n<lut.length;++n)
	    lut[n] = max; 
    }


}  // ClampedColorModel end

