package ij.process;

import java.util.*;
import java.awt.*;
import java.awt.image.*;
import ij.gui.*;

/** This is an 32-bit floating-point image and methods that operate on that image. */
public class FloatProcessor extends ImageProcessor {

    private float min, max, snapshotMin, snapshotMax;
    private float[] pixels;
    private byte[] pixels8;
    private float[] snapshotPixels = null;
    private byte[] LUT = null;
    private float fillColor =  Float.MAX_VALUE;
    //private float bgColor = Float.MIN_VALUE;
    private boolean fixedScale = false;

    /** Creates a new FloatProcessor using the specified pixel array and ColorModel.
        Set 'cm' to null to use the default grayscale LUT. */
    public FloatProcessor(int width, int height, float[] pixels, ColorModel cm) {
        if (pixels!=null && width*height!=pixels.length)
            throw new IllegalArgumentException(WRONG_LENGTH);
        this.width = width;
        this.height = height;
        this.pixels = pixels;
        this.cm = cm;
        resetRoi();
        if (pixels!=null)
            findMinAndMax();
    }

    /** Creates a blank FloatProcessor using the default grayscale LUT that
        displays zero as black. Call invertLut() to display zero as white. */
    public FloatProcessor(int width, int height) {
        this(width, height, new float[width*height], null);
    }

    /** Creates a FloatProcessor from an int array using the default grayscale LUT. */
    public FloatProcessor(int width, int height, int[] pixels) {
        this(width, height);
        for (int i=0; i<pixels.length; i++)
            this.pixels[i] = (float)(pixels[i]);
        findMinAndMax();
    }
    
    /** Creates a FloatProcessor from a double array using the default grayscale LUT. */
    public FloatProcessor(int width, int height, double[] pixels) {
        this(width, height);
        for (int i=0; i<pixels.length; i++)
            this.pixels[i] = (float)pixels[i];
        findMinAndMax();
    }
    
    /** Creates a FloatProcessor from a float[][] array using the default LUT*/
    public FloatProcessor(float[][] array) {
        width = array.length;
        height = array[0].length;
        pixels = new float[width*height];
        int i=0;
        for (int y=0; y<height; y++) {
            for (int x=0; x<width; x++) {
                pixels[i++] = array[x][y];
            }
        }
        resetRoi();
        findMinAndMax();
    }

    /**
    Calculates the minimum and maximum pixel value for the entire image. 
    Returns without doing anything if fixedScale has been set true as a result
    of calling setMinAndMax(). In this case, getMin() and getMax() return the
    fixed min and max defined by setMinAndMax(), rather than the calculated min
    and max.
    @see #getMin()
    @see #getMin()
    */
    public void findMinAndMax() {
        if (fixedScale)
            return;
        min = Float.MAX_VALUE;
        max = -Float.MAX_VALUE;
        for (int i=0; i < width*height; i++) {
            float value = pixels[i];
            if (!Float.isInfinite(value)) {
                if (value<min)
                    min = value;
                if (value>max)
                    max = value;
            }
        }
        pixelsModified = true;
        hideProgress();
    }

    /**
    Sets the min and max variables that control how real
    pixel values are mapped to 0-255 screen values. Use
    resetMinAndMax() to enable auto-scaling;
    @see ij.plugin.frame.ContrastAdjuster 
    */
    public void setMinAndMax(double min, double max) {
        if (min==0.0 && max==0.0)
            {resetMinAndMax(); return;}
        this.min = (float)min;
        this.max = (float)max;
        fixedScale = true;
        resetThreshold();
    }

    /** Recalculates the min and max values used to scale pixel
        values to 0-255 for display. This ensures that this 
        FloatProcessor is set up to correctly display the image. */
    public void resetMinAndMax() {
        fixedScale = false;
        findMinAndMax();
        resetThreshold();
    }

    /** Returns the smallest displayed pixel value. */
    public double getMin() {
        return min;
    }

    /** Returns the largest displayed pixel value. */
    public double getMax() {
        return max;
    }

    public Image createImage() {
        boolean firstTime = pixels8==null;
        if (firstTime || !lutAnimation) {
            // scale from float to 8-bits
            int size = width*height;
            if (pixels8==null)
                pixels8 = new byte[size];
            float value;
            int ivalue;
            float scale = 255f/(max-min);
            for (int i=0; i<size; i++) {
                value = pixels[i]-min;
                if (value<0f) value = 0f;
                ivalue = (int)(value*scale);
                if (ivalue>255) ivalue = 255;
                pixels8[i] = (byte)ivalue;
            }
        }
        if (cm==null)
            makeDefaultColorModel();
        if (source==null || (ij.IJ.isMacintosh()&&(!ij.IJ.isJava2()||lutAnimation))) {
            source = new MemoryImageSource(width, height, cm, pixels8, 0, width);
            source.setAnimated(true);
            source.setFullBufferUpdates(true);
            img = Toolkit.getDefaultToolkit().createImage(source);
        } else if (newPixels) {
            source.newPixels(pixels8, cm, 0, width);
            newPixels = false;
        } else
            source.newPixels();
        lutAnimation = false;
        return img;
    }
    
    /** Returns a new, blank FloatProcessor with the specified width and height. */
    public ImageProcessor createProcessor(int width, int height) {
        ImageProcessor ip2 = new FloatProcessor(width, height, new float[width*height], getColorModel());
        ip2.setMinAndMax(getMin(), getMax());
        return ip2;
    }

    public void snapshot() {
        snapshotWidth=width;
        snapshotHeight=height;
        snapshotMin=min;
        snapshotMax=max;
        if (snapshotPixels==null || (snapshotPixels!=null && snapshotPixels.length!=pixels.length))
            snapshotPixels = new float[width * height];
        System.arraycopy(pixels, 0, snapshotPixels, 0, width*height);
        pixelsModified = false;
        newSnapshot = true;
    }
    
    public void reset() {
        if (snapshotPixels==null)
            return;
        min=snapshotMin;
        max=snapshotMax;
        System.arraycopy(snapshotPixels,0,pixels,0,width*height);
    }
    
    public void reset(ImageProcessor mask) {
        if (mask==null || snapshotPixels==null)
            return; 
        if (mask.getWidth()!=roiWidth||mask.getHeight()!=roiHeight)
            throw new IllegalArgumentException(maskSizeError(mask));
        byte[] mpixels = (byte[])mask.getPixels();
        for (int y=roiY, my=0; y<(roiY+roiHeight); y++, my++) {
            int i = y * width + roiX;
            int mi = my * roiWidth;
            for (int x=roiX; x<(roiX+roiWidth); x++) {
                if (mpixels[mi++]==0)
                    pixels[i] = snapshotPixels[i];
                i++;
            }
        }
        newSnapshot = true;
    }

    /** Returns a pixel value that must be converted using
        Float.intBitsToFloat(). */
    public int getPixel(int x, int y) {
        if (x>=0 && x<width && y>=0 && y<height)
            return Float.floatToIntBits(pixels[y*width+x]);
        else
            return 0;
    }

    /** Returns the value of the pixel at (x,y) in a
        one element int array. iArray is an optiona
        preallocated array. */
    public int[] getPixel(int x, int y, int[] iArray) {
        if (iArray==null) iArray = new int[1];
        iArray[0] = (int)getPixelValue(x, y);
        return iArray;
    }

    /** Sets a pixel in the image using a one element int array. */
    public void putPixel(int x, int y, int[] iArray) {
        putPixelValue(x, y, iArray[0]);
    }

    /** Uses bilinear interpolation to find the pixel value at real coordinates (x,y). */
    public double getInterpolatedPixel(double x, double y) {
        if (x<0.0) x = 0.0;
        if (x>=width-1.0) x = width-1.001;
        if (y<0.0) y = 0.0;
        if (y>=height-1.0) y = height-1.001;
        return getInterpolatedPixel(x, y, pixels);
    }
        
    /** Stores the specified value at (x,y). The value is expected to be a
        float that has been converted to an int using Float.floatToIntBits(). */
    public void putPixel(int x, int y, int value) {
        if (x>=0 && x<width && y>=0 && y<height)
            pixels[y*width + x] = Float.intBitsToFloat(value);
    }

    /** Stores the specified real value at (x,y). */
    public void putPixelValue(int x, int y, double value) {
        if (x>=0 && x<width && y>=0 && y<height)
            pixels[y*width + x] = (float)value;
    }

    /** Returns the value of the pixel at (x,y) as a float. */
    public float getPixelValue(int x, int y) {
        if (x>=0 && x<width && y>=0 && y<height)
            return pixels[y*width + x];
        else
            return 0f;
    }

    /** Draws a pixel in the current foreground color. */
    public void drawPixel(int x, int y) {
        if (x>=clipXMin && x<=clipXMax && y>=clipYMin && y<=clipYMax)
            putPixel(x, y, Float.floatToIntBits(fillColor));
    }

    /** Returns a reference to the float array containing
        this image's pixel data. */
    public Object getPixels() {
        return (Object)pixels;
    }

    public Object getPixelsCopy() {
        if (newSnapshot && snapshotPixels!=null)
            return snapshotPixels;
        else {
            float[] pixels2 = new float[width*height];
            System.arraycopy(pixels, 0, pixels2, 0, width*height);
            return pixels2;
        }
    }

    public void setPixels(Object pixels) {
        this.pixels = (float[])pixels;
        resetPixels(pixels);
        snapshotPixels = null;
        if (pixels==null)
            pixels8 = null;
    }

    /** Copies the image contained in 'ip' to (xloc, yloc) using one of
        the transfer modes defined in the Blitter interface. */
    public void copyBits(ImageProcessor ip, int xloc, int yloc, int mode) {
        //if (!(ip instanceof FloatProcessor))
        //  throw new IllegalArgumentException("32-bit (real) image required");
        new FloatBlitter(this).copyBits(ip, xloc, yloc, mode);
    }

    public void applyTable(int[] lut) {}

    private float[] getCopyOfPixels() {
        if (pixelsModified) {
            float[] pixelsCopy = new float[width * height];
            System.arraycopy(pixels, 0, pixelsCopy, 0, width*height);
            return pixelsCopy;
        }
        else
            return snapshotPixels;
    }
    
    private void process(int op, double value) {
        float c, v1, v2;
        boolean resetMinMax = roiWidth==width && roiHeight==height && !(op==FILL);
        c = (float)value;
        for (int y=roiY; y<(roiY+roiHeight); y++) {
            int i = y * width + roiX;
            for (int x=roiX; x<(roiX+roiWidth); x++) {
                v1 = pixels[i];
                switch(op) {
                    case INVERT:
                        v2 = max - (v1 - min);
                        break;
                    case FILL:
                        v2 = fillColor;
                        break;
                    case ADD:
                        v2 = v1 + c;
                        break;
                    case MULT:
                        v2 = v1 * c;
                        break;
                    case GAMMA:
                        if (v1<=0f)
                            v2 = 0f;
                        else
                            v2 = (float)Math.exp(c*Math.log(v1));
                        break;
                    case LOG:
                        if (v1<=0f)
                            v2 = 0f;
                        else
                            v2 = (float)Math.log(v1);
                        break;
                    case EXP:
                        v2 = (float)Math.exp(v1);
                        break;
                    case SQR:
                            v2 = v1*v1;
                        break;
                    case SQRT:
                        if (v1<=0f)
                            v2 = 0f;
                        else
                            v2 = (float)Math.sqrt(v1);
                        break;
                    case MINIMUM:
                        if (v1<value)
                            v2 = (float)value;
                        else
                            v2 = v1;
                        break;
                    case MAXIMUM:
                        if (v1>value)
                            v2 = (float)value;
                        else
                            v2 = v1;
                        break;
                     default:
                        v2 = v1;
                }
                pixels[i++] = v2;
            }
            if (y%20==0)
                showProgress((double)(y-roiY)/roiHeight);
        }
        if (resetMinMax)
            findMinAndMax();
    }

    public void invert() {process(INVERT, 0.0);}
    public void add(int value) {process(ADD, value);}
    public void add(double value) {process(ADD, value);}
    public void multiply(double value) {process(MULT, value);}
    public void and(int value) {}
    public void or(int value) {}
    public void xor(int value) {}
    public void gamma(double value) {process(GAMMA, value);}
    public void log() {process(LOG, 0.0);}
    public void exp() {process(EXP, 0.0);}
    public void sqr() {process(SQR, 0.0);}
    public void sqrt() {process(SQRT, 0.0);}
    public void min(double value) {process(MINIMUM, value);}
    public void max(double value) {process(MAXIMUM, value);}



    /** Fills the current rectangular ROI. */
    public void fill() {process(FILL, 0.0);}

    /** Fills pixels that are within roi and part of the mask.
        Throws an IllegalArgumentException if the mask is null or
        the size of the mask is not the same as the size of the ROI. */
    public void fill(ImageProcessor mask) {
        if (mask==null)
            {fill(); return;}
        if (mask.getWidth()!=roiWidth||mask.getHeight()!=roiHeight)
            throw new IllegalArgumentException(maskSizeError(mask));
        byte[] mpixels = (byte[])mask.getPixels();
        for (int y=roiY, my=0; y<(roiY+roiHeight); y++, my++) {
            int i = y * width + roiX;
            int mi = my * roiWidth;
            for (int x=roiX; x<(roiX+roiWidth); x++) {
                if (mpixels[mi++]!=0)
                    pixels[i] = fillColor;
                i++;
            }
        }
    }

    /** 3x3 convolution contributed by Glynne Casteel. */
    public void convolve3x3(int[] kernel) {
        float p1, p2, p3, p4, p5, p6, p7, p8, p9;
        float k1=kernel[0], k2=kernel[1], k3=kernel[2],
              k4=kernel[3], k5=kernel[4], k6=kernel[5],
              k7=kernel[6], k8=kernel[7], k9=kernel[8];

        float scale = 0f;
        for (int i=0; i<kernel.length; i++)
            scale += kernel[i];
        if (scale==0) scale = 1f;
        int inc = roiHeight/25;
        if (inc<1) inc = 1;
        
        float[] pixels2 = (float[])getPixelsCopy();
        int offset;
        float sum;
        int rowOffset = width;
        for (int y=yMin; y<=yMax; y++) {
            offset = xMin + y * width;
            p1 = 0f;
            p2 = pixels2[offset-rowOffset-1];
            p3 = pixels2[offset-rowOffset];
            p4 = 0f;
            p5 = pixels2[offset-1];
            p6 = pixels2[offset];
            p7 = 0f;
            p8 = pixels2[offset+rowOffset-1];
            p9 = pixels2[offset+rowOffset];

            for (int x=xMin; x<=xMax; x++) {
                p1 = p2; p2 = p3;
                p3 = pixels2[offset-rowOffset+1];
                p4 = p5; p5 = p6;
                p6 = pixels2[offset+1];
                p7 = p8; p8 = p9;
                p9 = pixels2[offset+rowOffset+1];
                sum = k1*p1 + k2*p2 + k3*p3
                    + k4*p4 + k5*p5 + k6*p6
                    + k7*p7 + k8*p8 + k9*p9;
                sum /= scale;
                pixels[offset++] = sum;
            }
            if (y%inc==0)
                showProgress((double)(y-roiY)/roiHeight);
        }
        hideProgress();
    }

    /** Filters using a 3x3 neighborhood. */
    public void filter(int type) {
        float p1, p2, p3, p4, p5, p6, p7, p8, p9;
        int inc = roiHeight/25;
        if (inc<1) inc = 1;
        
        float[] pixels2 = (float[])getPixelsCopy();
        int offset;
        float sum1, sum2;
        int rowOffset = width;
        for (int y=yMin; y<=yMax; y++) {
            offset = xMin + y * width;
            p1 = 0f;
            p2 = pixels2[offset-rowOffset-1];
            p3 = pixels2[offset-rowOffset];
            p4 = 0f;
            p5 = pixels2[offset-1];
            p6 = pixels2[offset];
            p7 = 0f;
            p8 = pixels2[offset+rowOffset-1];
            p9 = pixels2[offset+rowOffset];

            for (int x=xMin; x<=xMax; x++) {
                p1 = p2; p2 = p3;
                p3 = pixels2[offset-rowOffset+1];
                p4 = p5; p5 = p6;
                p6 = pixels2[offset+1];
                p7 = p8; p8 = p9;
                p9 = pixels2[offset+rowOffset+1];

                switch (type) {
                    case BLUR_MORE:
                        pixels[offset++] = (p1+p2+p3+p4+p5+p6+p7+p8+p9)/9f;
                        break;
                    case FIND_EDGES:
                        sum1 = p1 + 2*p2 + p3 - p7 - 2*p8 - p9;
                        sum2 = p1  + 2*p4 + p7 - p3 - 2*p6 - p9;
                        pixels[offset++] = (float)Math.sqrt(sum1*sum1 + sum2*sum2);
                        break;
                }
            }
            if (y%inc==0)
                showProgress((double)(y-roiY)/roiHeight);
        }
        if (type==BLUR_MORE)
            hideProgress();
        else
            findMinAndMax();
    }

    /** Rotates the image or ROI 'angle' degrees clockwise.
        @see ImageProcessor#setInterpolate
    */
    public void rotate(double angle) {
        float[] pixels2 = (float[])getPixelsCopy();
        double centerX = roiX + (roiWidth-1)/2.0;
        double centerY = roiY + (roiHeight-1)/2.0;
        int xMax = roiX + this.roiWidth - 1;
        
        double angleRadians = -angle/(180.0/Math.PI);
        double ca = Math.cos(angleRadians);
        double sa = Math.sin(angleRadians);
        double tmp1 = centerY*sa-centerX*ca;
        double tmp2 = -centerX*sa-centerY*ca;
        double tmp3, tmp4, xs, ys;
        int index, ixs, iys;
        double dwidth=width,dheight=height;
        double xlimit = width-1.0, xlimit2 = width-1.001;
        double ylimit = height-1.0, ylimit2 = height-1.001;
        
        for (int y=roiY; y<(roiY + roiHeight); y++) {
            index = y*width + roiX;
            tmp3 = tmp1 - y*sa + centerX;
            tmp4 = tmp2 + y*ca + centerY;
            for (int x=roiX; x<=xMax; x++) {
                xs = x*ca + tmp3;
                ys = x*sa + tmp4;
                if ((xs>=-0.01) && (xs<dwidth) && (ys>=-0.01) && (ys<dheight)) {
                    if (interpolate) {
                        if (xs<0.0) xs = 0.0;
                        if (xs>=xlimit) xs = xlimit2;
                        if (ys<0.0) ys = 0.0;           
                        if (ys>=ylimit) ys = ylimit2;
                        pixels[index++] = (float)getInterpolatedPixel(xs, ys, pixels2);
                    } else {
                        ixs = (int)(xs+0.5);
                        iys = (int)(ys+0.5);
                        if (ixs>=width) ixs = width - 1;
                        if (iys>=height) iys = height -1;
                        pixels[index++] = pixels2[width*iys+ixs];
                    }
                } else
                    pixels[index++] = 0;
            }
            if (y%20==0)
            showProgress((double)(y-roiY)/roiHeight);
        }
        hideProgress();
    }

    public void flipVertical() {
        int index1,index2;
        float tmp;
        for (int y=0; y<roiHeight/2; y++) {
            index1 = (roiY+y)*width+roiX;
            index2 = (roiY+roiHeight-1-y)*width+roiX;
            for (int i=0; i<roiWidth; i++) {
                tmp = pixels[index1];
                pixels[index1++] = pixels[index2];
                pixels[index2++] = tmp;
            }
        }
        newSnapshot = false;
    }
    
    public void noise(double range) {
        Random rnd=new Random();
        for (int y=roiY; y<(roiY+roiHeight); y++) {
            int i = y * width + roiX;
            for (int x=roiX; x<(roiX+roiWidth); x++) {
                float RandomBrightness = (float)(rnd.nextGaussian()*range);
                pixels[i] = pixels[i] + RandomBrightness;
                i++;
            }
        }
        resetMinAndMax();
    }

    public ImageProcessor crop() {
        ImageProcessor ip2 = createProcessor(roiWidth, roiHeight);
        float[] pixels2 = (float[])ip2.getPixels();
        for (int ys=roiY; ys<roiY+roiHeight; ys++) {
            int offset1 = (ys-roiY)*roiWidth;
            int offset2 = ys*width+roiX;
            for (int xs=0; xs<roiWidth; xs++)
                pixels2[offset1++] = pixels[offset2++];
        }
        return ip2;
    }
    
    /** Returns a duplicate of this image. */ 
    public synchronized ImageProcessor duplicate() { 
        ImageProcessor ip2 = createProcessor(width, height); 
        float[] pixels2 = (float[])ip2.getPixels(); 
        System.arraycopy(pixels, 0, pixels2, 0, width*height); 
        return ip2; 
    } 

    /** Scales the image or selection using the specified scale factors.
        @see ImageProcessor#setInterpolate
    */
    public void scale(double xScale, double yScale) {
        double xCenter = roiX + roiWidth/2.0;
        double yCenter = roiY + roiHeight/2.0;
        int xmin, xmax, ymin, ymax;
        
        if ((xScale>1.0) && (yScale>1.0)) {
            //expand roi
            xmin = (int)(xCenter-(xCenter-roiX)*xScale);
            if (xmin<0) xmin = 0;
            xmax = xmin + (int)(roiWidth*xScale) - 1;
            if (xmax>=width) xmax = width - 1;
            ymin = (int)(yCenter-(yCenter-roiY)*yScale);
            if (ymin<0) ymin = 0;
            ymax = ymin + (int)(roiHeight*yScale) - 1;
            if (ymax>=height) ymax = height - 1;
        } else {
            xmin = roiX;
            xmax = roiX + roiWidth - 1;
            ymin = roiY;
            ymax = roiY + roiHeight - 1;
        }
        float[] pixels2 = (float[])getPixelsCopy();
        boolean checkCoordinates = (xScale < 1.0) || (yScale < 1.0);
        int index1, index2, xsi, ysi;
        double ys, xs;
        double xlimit = width-1.0, xlimit2 = width-1.001;
        double ylimit = height-1.0, ylimit2 = height-1.001;
        for (int y=ymin; y<=ymax; y++) {
            ys = (y-yCenter)/yScale + yCenter;
            ysi = (int)ys;
            if (ys<0.0) ys = 0.0;           
            if (ys>=ylimit) ys = ylimit2;
            index1 = y*width + xmin;
            index2 = width*(int)ys;
            for (int x=xmin; x<=xmax; x++) {
                xs = (x-xCenter)/xScale + xCenter;
                xsi = (int)xs;
                if (checkCoordinates && ((xsi<xmin) || (xsi>xmax) || (ysi<ymin) || (ysi>ymax)))
                    pixels[index1++] = (float)min;
                else {
                    if (interpolate) {
                        if (xs<0.0) xs = 0.0;
                        if (xs>=xlimit) xs = xlimit2;
                        pixels[index1++] = (float)getInterpolatedPixel(xs, ys, pixels2);
                    } else
                        pixels[index1++] = pixels2[index2+xsi];
                }
            }
            if (y%20==0)
            showProgress((double)(y-ymin)/height);
        }
        hideProgress();
    }

    /** Uses bilinear interpolation to find the pixel value at real coordinates (x,y). */
    private final double getInterpolatedPixel(double x, double y, float[] pixels) {
        int xbase = (int)x;
        int ybase = (int)y;
        double xFraction = x - xbase;
        double yFraction = y - ybase;
        int offset = ybase * width + xbase;
        double lowerLeft = pixels[offset];
        double lowerRight = pixels[offset + 1];
        double upperRight = pixels[offset + width + 1];
        double upperLeft = pixels[offset + width];
        double upperAverage = upperLeft + xFraction * (upperRight - upperLeft);
        double lowerAverage = lowerLeft + xFraction * (lowerRight - lowerLeft);
        return lowerAverage + yFraction * (upperAverage - lowerAverage);
    }

    /** Creates a new FloatProcessor containing a scaled copy of this image or selection. */
    public ImageProcessor resize(int dstWidth, int dstHeight) {
        double srcCenterX = roiX + roiWidth/2.0;
        double srcCenterY = roiY + roiHeight/2.0;
        double dstCenterX = dstWidth/2.0;
        double dstCenterY = dstHeight/2.0;
        double xScale = (double)dstWidth/roiWidth;
        double yScale = (double)dstHeight/roiHeight;
        if (interpolate) {
            dstCenterX += xScale/2.0;
            dstCenterY += yScale/2.0;
        }
        ImageProcessor ip2 = createProcessor(dstWidth, dstHeight);
        float[] pixels2 = (float[])ip2.getPixels();
        double xs, ys;
        double xlimit = width-1.0, xlimit2 = width-1.001;
        double ylimit = height-1.0, ylimit2 = height-1.001;
        int index1, index2;
        for (int y=0; y<=dstHeight-1; y++) {
            ys = (y-dstCenterY)/yScale + srcCenterY;
            if (interpolate) {
                if (ys<0.0) ys = 0.0;
                if (ys>=ylimit) ys = ylimit2;
            }
            index1 = width*(int)ys;
            index2 = y*dstWidth;
            for (int x=0; x<=dstWidth-1; x++) {
                xs = (x-dstCenterX)/xScale + srcCenterX;
                if (interpolate) {
                    if (xs<0.0) xs = 0.0;
                    if (xs>=xlimit) xs = xlimit2;
                    pixels2[index2++] = (float)getInterpolatedPixel(xs, ys, pixels);
                } else
                    pixels2[index2++] = pixels[index1+(int)xs];
            }
            if (y%20==0)
            showProgress((double)y/dstHeight);
        }
        hideProgress();
        return ip2;
    }

    /** Sets the foreground fill/draw color. */
    public void setColor(Color color) {
        int bestIndex = getBestIndex(color);
        if (bestIndex>0 && getMin()==0.0 && getMax()==0.0) {
            fillColor = bestIndex;
            setMinAndMax(0.0,255.0);
        } else if (bestIndex==0 && getMin()>0.0 && (color.getRGB()&0xffffff)==0)
            fillColor = 0f;
        else
            fillColor = (float)(min + (max-min)*(bestIndex/255.0));
    }
    
    /** Sets the default fill/draw value. */
    public void setValue(double value) {
        fillColor = (float)value;
    }

    /** Does nothing. The rotate() and scale() methods always zero fill. */
    public void setBackgroundValue(double value) {
    }

    public void setThreshold(double minThreshold, double maxThreshold, int lutUpdate) {
        if (minThreshold!=NO_THRESHOLD && max>min) {
            double minT = Math.round(((minThreshold-min)/(max-min))*255.0);
            double maxT = Math.round(((maxThreshold-min)/(max-min))*255.0);
            super.setThreshold(minT, maxT, lutUpdate);
            this.minThreshold = minThreshold;
            this.maxThreshold = maxThreshold;
        } else
            super.resetThreshold();
    }

    /** Performs a convolution operation using the specified kernel. */
    public void convolve(float[] kernel, int kernelWidth, int kernelHeight) {
        snapshot();
        new ij.plugin.filter.Convolver().convolve(this, kernel, kernelWidth, kernelHeight);
    }

    /** Not implemented. */
    public void threshold(int level) {}
    /** Not implemented. */
    public void autoThreshold() {}
    /** Not implemented. */
    public void medianFilter() {}
    /** Not implemented. */
    public int[] getHistogram() {return null;}
    /** Not implemented. */
    public void erode() {}
    /** Not implemented. */
    public void dilate() {}


}