PixelLoader.java

package ac.essex.ooechs.imaging.commons; 
 
import ac.essex.ooechs.imaging.commons.texture.Line; 
import ac.essex.ooechs.imaging.commons.texture.Perimeter; 
import ac.essex.ooechs.imaging.commons.fast.FastStatistics; 
 
import java.awt.*; 
import java.awt.image.BufferedImage; 
import java.io.File; 
 
/** 
 * Loads Pixels from an image file. 
 * 
 * @version 1.2 March 2008. A number of performance enhancements to make it run 
 *          more quickly. 
 */ 
public class PixelLoader implements Cloneable, SimpleImage { 
 
    protected BufferedImage img; 
 
    protected File file = null; 
 
    public boolean loadedOK = true; 
 
    /** 
     * Private constructor for the pixel loader 
     */ 
    private PixelLoader() { 
        loadedOK = true; 
    } 
 
    /** 
     * Load a blank PixelLoader image that can subsequently 
     * be manipulated. 
     */ 
    public PixelLoader(int width, int height) { 
        this.file = null; 
        img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); 
        loadedOK = true; 
    } 
 
    public PixelLoader(String filename) throws Exception { 
        this(new File(filename)); 
    } 
 
    public PixelLoader(File imageFile) throws Exception { 
 
        // ensure that the file actually exists before proceeding 
        if (!imageFile.exists()) throw new Exception("File does not exist: " + imageFile.getAbsolutePath()); 
 
        try { 
 
            this.file = imageFile; 
 
            // Use the Java ImageIO library to open the file as a buffered image. 
            img = javax.imageio.ImageIO.read(imageFile); 
 
        } catch (javax.imageio.IIOException e1) { 
 
            e1.printStackTrace(); 
 
            loadedOK = false; 
 
        } catch (Exception e) { 
 
            e.printStackTrace(); 
 
            loadedOK = false; 
 
        } 
 
        if (img == null) { 
            throw new RuntimeException("No Buffered Image in Pixel Loader. Image may not have loaded OK. Try a different file type?"); 
        } 
 
    } 
 
    public PixelLoader(BufferedImage img) { 
        this.img = img; 
        loadedOK = true; 
    } 
 
    public Object clone() throws CloneNotSupportedException { 
        return super.clone(); 
    } 
 
    protected Perimeter perimeter1; 
    protected Perimeter perimeter2; 
 
    public Perimeter getPerimeter1() { 
        if (perimeter1 == null) { 
            perimeter1 = new Perimeter(2); 
        } 
        return perimeter1; 
    } 
 
    public Perimeter getPerimeter2() { 
        if (perimeter2 == null) { 
            perimeter2 = new Perimeter(5); 
        } 
        return perimeter2; 
    } 
 
    protected Line hLine1, hLine2; 
    protected Line vLine1, vLine2; 
 
    public Line getHLine1() { 
        if (hLine1 == null) { 
            hLine1 = new Line(2, Line.HORIZONTAL); 
        } 
        return hLine1; 
    } 
 
    public Line getHLine2() { 
        if (hLine2 == null) { 
            hLine2 = new Line(5, Line.HORIZONTAL); 
        } 
        return hLine2; 
    } 
 
    public Line getVLine1() { 
        if (vLine1 == null) { 
            vLine1 = new Line(2, Line.VERTICAL); 
        } 
        return vLine1; 
    } 
 
    public Line getVLine2() { 
        if (vLine2 == null) { 
            vLine2 = new Line(5, Line.VERTICAL); 
        } 
        return vLine2; 
    } 
 
    private int lightnessMean = -1; 
    private double lightnessStdDeviation = -1; 
    private int hueMean = -1; 
    private double hueStdDeviation = -1; 
    private int satMean = -1; 
    private double satStdDeviation = -1; 
    private double greyStdDeviation = -1; 
 
    public int getLightnessMean() { 
        if (lightnessMean == -1) calculateImageStatistics(); 
        return lightnessMean; 
    } 
 
    public double getLightnessStdDeviation() { 
        if (lightnessStdDeviation == -1) calculateImageStatistics(); 
        return lightnessStdDeviation; 
    } 
 
    public int getHueMean() { 
        if (hueMean == -1) calculateImageStatistics(); 
        return hueMean; 
    } 
 
    public double getHueStdDeviation() { 
        if (hueStdDeviation == -1) calculateImageStatistics(); 
        return hueStdDeviation; 
    } 
 
    public int getSatMean() { 
        if (satMean == -1) calculateImageStatistics(); 
        return satMean; 
    } 
 
    public double getSatStdDeviation() { 
        if (satStdDeviation == -1) calculateImageStatistics(); 
        return satStdDeviation; 
    } 
 
    /** 
     * Gets the standard deviation of greyness of the whole image. 
     * Optimised. Was 51ms, now 7ms. 
     */ 
    public double getStdDeviation() { 
        if (greyStdDeviation == -1) { 
            FastStatistics s = new FastStatistics(); 
            for (int y = 0; y < getHeight(); y += 1) { 
                for (int x = 0; x < getWidth(); x += 1) { 
                    s.addData(getGreyValue(x, y)); 
                } 
            } 
            greyStdDeviation = s.getStandardDeviation(); 
        } 
        return greyStdDeviation; 
    } 
 
    /** 
     * Optimised. 
     * Was 461ms, now 17ms (!) 
     */ 
    private void calculateImageStatistics() { 
 
        FastStatistics hueSolver = new FastStatistics(); 
        FastStatistics saturationSolver = new FastStatistics(); 
        FastStatistics lightnessSolver = new FastStatistics(); 
 
        for (int y = 0; y < getHeight(); y += 1) { 
            for (int x = 0; x < getWidth(); x += 1) { 
 
                hueSolver.addData(getHue(x, y)); 
                saturationSolver.addData(getSaturation(x, y)); 
                lightnessSolver.addData(getLightness(x, y)); 
 
            } 
        } 
 
 
        lightnessMean = (int) lightnessSolver.getMean(); 
        lightnessStdDeviation = (int) lightnessSolver.getStandardDeviation(); 
 
        hueMean = (int) hueSolver.getMean(); 
        hueStdDeviation = (int) hueSolver.getStandardDeviation(); 
 
        satMean = (int) saturationSolver.getMean(); 
        satStdDeviation = (int) saturationSolver.getStandardDeviation(); 
 
 
    } 
 
    /** 
     * Converts the image to a saturation map. 
     */ 
    public PixelLoader getSaturationMap() { 
 
        PixelLoader newImage = new PixelLoader(); 
        newImage.file = this.file; 
        newImage.img = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB); 
 
        for (int y = 0; y < getHeight(); y += 1) { 
            for (int x = 0; x < getWidth(); x += 1) { 
 
                // get the colour 
                Color c = getColor(x, y); 
 
                // get hue/sat/lightness 
                int[] hsl = ColourConvertor.RGB2HSL(c.getRed(), c.getGreen(), c.getBlue()); 
 
                // get saturation 
                int saturation = hsl[1]; 
 
                // convert back to greyscale RGB 
                c = new Color(saturation, saturation, saturation); 
 
                // save the pixel 
                newImage.setRGB(x, y, c.getRGB()); 
 
            } 
        } 
 
        return newImage; 
 
    } 
 
    public PixelLoader getSubImage(SegmentedArea area) { 
        PixelLoader newImage = new PixelLoader(); 
        newImage.file = this.file; 
        newImage.img = this.img.getSubimage(area.left, area.top, area.width, area.height); 
        return newImage; 
    } 
 
    /** 
     * Gets the buffered image which the PixelLoader is wrapping. 
     */ 
    public BufferedImage getBufferedImage() { 
        return img; 
    } 
 
    /** 
     * Returns statistics about a particular channel. 
     */ 
    public FastStatistics getStatistics(int channel) { 
        FastStatistics solver = new FastStatistics(); 
 
        for (int y = 0; y < getHeight(); y += 1) { 
            for (int x = 0; x < getWidth(); x += 1) { 
                switch (channel) { 
                    case ColourChannels.GREYSCALE: 
                        solver.addData(getGreyValue(x, y)); 
                        break; 
                    case ColourChannels.RED: 
                        solver.addData(getRed(x, y)); 
                        break; 
                    case ColourChannels.GREEN: 
                        solver.addData(getGreen(x, y)); 
                        break; 
                    case ColourChannels.BLUE: 
                        solver.addData(getBlue(x, y)); 
                        break; 
                } 
            } 
        } 
 
        return solver; 
 
    } 
 
    /** 
     * Gets the file from which the image was loaded. 
     */ 
    public File getFile() { 
        return file; 
    } 
 
    private int[][] greyColourCache; 
    private int[][] redColourCache; 
    private int[][] greenColourCache; 
    private int[][] blueColourCache; 
 
    /** 
     * Gets the colour at the point X, Y, as a java.awt.Color object 
     */ 
    public Color getColor(int x, int y) { 
        return new Color(img.getRGB(x, y)); 
    } 
 
    /** 
     * Gets the raw RGB color at the point X, Y. 
     */ 
    public int getRGB(int x, int y) { 
        return img.getRGB(x, y); 
    } 
 
    /** 
     * Gets the grayscale value of the pixel 
     */ 
    public int getGreyValue(int x, int y) throws RuntimeException { 
        // this is called many times for each pixel. Hence we'll do the maths once only. 
        if (greyColourCache == null) createRGBCache(); 
        return greyColourCache[y][x]; 
    } 
 
    public int getRed(int x, int y) throws RuntimeException { 
        // this is called many times for each pixel. Hence we'll do the maths once only. 
        if (redColourCache == null) createRGBCache(); 
        return redColourCache[y][x]; 
    } 
 
    public int getGreen(int x, int y) throws RuntimeException { 
        // this is called many times for each pixel. Hence we'll do the maths once only. 
        if (greenColourCache == null) createRGBCache(); 
        return greenColourCache[y][x]; 
    } 
 
    public int getBlue(int x, int y) throws RuntimeException { 
        // this is called many times for each pixel. Hence we'll do the maths once only. 
        if (blueColourCache == null) createRGBCache(); 
        return blueColourCache[y][x]; 
    } 
 
    /** 
     * Optimised. 
     * Before: 140ms on Lenna.png; after: 82 ms. 
     */ 
    private void createRGBCache() { 
 
        final double rx = 0.3; 
        final double gx = 0.59; 
        final double bx = 0.11; 
 
        greyColourCache = new int[img.getHeight()][img.getWidth()]; 
        redColourCache = greyColourCache.clone(); 
        greenColourCache = greyColourCache.clone(); 
        blueColourCache = greyColourCache.clone(); 
 
        for (int y = 0; y < img.getHeight(); y++) { 
 
            for (int x = 0; x < img.getWidth(); x++) { 
 
                // get a colour object, which saves us having to shift bits and other stuff. 
                int rgb = img.getRGB(x, y); 
 
                // extract the colours 
                int red = (rgb >> 16) & 0xFF; 
                int green = (rgb >> 8) & 0xFF; 
                int blue = rgb & 0xFF; 
 
                greyColourCache[y][x] = (int) ((red * rx) + (green * gx) + (blue * bx)); 
                redColourCache[y][x] = red; 
                greenColourCache[y][x] = green; 
                blueColourCache[y][x] = blue; 
 
            } 
 
        } 
    } 
 
    private int[][] hueCache; 
    private int[][] satCache; 
    private int[][] lightnessCache; 
 
    public int getHue(int x, int y) { 
        if (hueCache == null) createHSLCache(); 
        return hueCache[y][x]; 
    } 
 
    public int getSaturation(int x, int y) { 
        if (satCache == null) createHSLCache(); 
        return satCache[y][x]; 
    } 
 
    public int getLightness(int x, int y) { 
        if (lightnessCache == null) createHSLCache(); 
        return lightnessCache[y][x]; 
    } 
 
    /** 
     * Optimised. 
     * Previous: 168ms; After: 141ms 
     */ 
    private void createHSLCache() { 
 
        hueCache = new int[img.getHeight()][img.getWidth()]; 
        satCache = hueCache.clone(); 
        lightnessCache = hueCache.clone(); 
 
        for (int yPos = 0; yPos < img.getHeight(); yPos++) { 
            for (int xPos = 0; xPos < img.getWidth(); xPos++) { 
 
                int[] hsl = ColourConvertor.RGB2HSL(img.getRGB(xPos, yPos)); 
 
                hueCache[yPos][xPos] = hsl[0]; 
                satCache[yPos][xPos] = hsl[1]; 
                lightnessCache[yPos][xPos] = hsl[2]; 
 
            } 
 
        } 
 
    } 
 
    private ConvolutionMatrix verticalSobel; 
    private ConvolutionMatrix horizontalSobel; 
    private ConvolutionMatrix laplacian; 
 
    private int[][] vsobelCache; 
    private int[][] hsobelCache; 
    private int[][] laplacianCache; 
 
    public int getVerticalSobel(int x, int y) { 
        if (vsobelCache == null) { 
            vsobelCache = new int[getWidth()][getHeight()]; 
            for (int dY = 0; dY < getHeight(); dY++) 
                for (int dX = 0; dX < getWidth(); dX++) { 
                    vsobelCache[dX][dY] = -1; 
                } 
            verticalSobel = new ConvolutionMatrix(ConvolutionMatrix.VERTICAL_SOBEL); 
        } 
        if (vsobelCache[x][y] == -1) { 
            vsobelCache[x][y] = getConvolved(x, y, verticalSobel); 
        } 
        return vsobelCache[x][y]; 
    } 
 
    public int getHorizontalSobel(int x, int y) { 
        if (hsobelCache == null) { 
            hsobelCache = new int[getWidth()][getHeight()]; 
            for (int dY = 0; dY < getHeight(); dY++) 
                for (int dX = 0; dX < getWidth(); dX++) { 
                    hsobelCache[dX][dY] = -1; 
                } 
            horizontalSobel = new ConvolutionMatrix(ConvolutionMatrix.HORIZONTAL_SOBEL); 
        } 
        if (hsobelCache[x][y] == -1) { 
            hsobelCache[x][y] = getConvolved(x, y, horizontalSobel); 
        } 
        return hsobelCache[x][y]; 
    } 
 
    public int getLaplacian(int x, int y) { 
        if (laplacianCache == null) { 
            laplacianCache = new int[getWidth()][getHeight()]; 
            for (int dY = 0; dY < getHeight(); dY++) 
                for (int dX = 0; dX < getWidth(); dX++) { 
                    laplacianCache[dX][dY] = -1; 
                } 
            laplacian = new ConvolutionMatrix(ConvolutionMatrix.LAPLACIAN); 
        } 
        if (laplacianCache[x][y] == -1) { 
            laplacianCache[x][y] = getConvolved(x, y, laplacian); 
        } 
        return laplacianCache[x][y]; 
    } 
 
    public float[][] varianceCache; 
 
    public static void main(String[] args) throws Exception { 
        PixelLoader image = new PixelLoader("/home/ooechs/Desktop/Lenna.png"); 
        FastStatistics s = new FastStatistics(); 
        for (int i = 0; i <= 20; i++) { 
            long start = System.currentTimeMillis(); 
            image.get3x3Mean(10, 10); 
            image.meanCache = null; 
            long end = System.currentTimeMillis(); 
            long time = end - start; 
            if (i > 0) s.addData(time); 
            System.out.println(time); 
        } 
        System.out.println(s.getMean() + " | " + s.getStandardDeviation()); 
    } 
 
    /** 
     * Gets the variance in grey value about a 3x3 window, centering on 
     * a specific point at (x, y). Pixels about the edge are ignored. Results cached for performance. 
     * Optimised. Was 248ms, now 87ms. 
     */ 
    public float get3x3Variance(int x, int y) { 
        if (varianceCache == null) { 
 
            FastStatistics solver = new FastStatistics(); 
 
            varianceCache = new float[getHeight()][getWidth()]; 
            for (int ny = size; ny < getHeight() - size; ny++) 
                for (int nx = size; nx < getWidth() - size; nx++) { 
 
                    solver.reset(); 
 
                    for (int dy = -size; dy <= size; dy++) { 
                        int newY = ny + dy; 
                        for (int dx = -size; dx <= size; dx++) { 
                            solver.addData(getGreyValue(nx + dx, newY)); 
                        } 
                    } 
 
                    varianceCache[ny][nx] = solver.getVariance(); 
 
                } 
        } 
        return varianceCache[y][x]; 
    } 
 
    /** 
     * The size of the nxn caches. 
     * Size 2 creates 5x5 ranges. 
     */ 
    private final int size = 2; 
 
    private int[][] rangeCache; 
 
    /** 
     * Optimised. 
     * Took 206 ms to run on Lenna.png, now takes 43ms. 
     */ 
    public int get3x3Range(int x, int y) { 
        if (rangeCache == null) { 
            rangeCache = new int[getHeight()][getWidth()]; 
            for (int ny = size; ny < getHeight() - size; ny++) { 
                for (int nx = size; nx < getWidth() - size; nx++) { 
 
                    // calculate the mean without using the statistics solver 
                    int high = 0; 
                    int low = 256; 
 
                    for (int dy = -size; dy <= size; dy++) { 
                        int newY = ny + dy; 
                        for (int dx = -size; dx <= size; dx++) { 
                            int value = getGreyValue(nx + dx, newY); 
                            if (value > high) high = value; 
                            if (value < low) low = value; 
                        } 
                    } 
 
                    rangeCache[ny][nx] = high - low; 
                } 
            } 
        } 
        return rangeCache[y][x]; 
    } 
 
    private float[][] meanCache; 
 
    /** 
     * Optimised. 
     * Was 246ms to run on Lenna.png, now is 57ms. 
     */ 
    public float get3x3Mean(int x, int y) { 
 
        if (meanCache == null) { 
 
            // precalculate the number of elements to be averaged 
            final int count = ((size * 2) + 1) * ((size * 2) + 1); 
 
            meanCache = new float[getHeight()][getWidth()]; 
            for (int ny = size; ny < getHeight() - size; ny++) { 
                for (int nx = size; nx < getWidth() - size; nx++) { 
 
                    // calculate the mean without using the statistics solver 
                    float total = 0; 
 
                    for (int dy = -size; dy <= size; dy++) { 
                        int newY = ny + dy; 
                        for (int dx = -size; dx <= size; dx++) { 
                            total += getGreyValue(nx + dx, newY); 
                        } 
                    } 
 
                    meanCache[ny][nx] = total / count; 
 
                } 
            } 
        } 
 
        return meanCache[y][x]; 
 
    } 
 
 
    public static final int HARALICK_CONTRAST = 1; 
    public static final int HARALICK_DISSIMILARITY = 2; 
    public static final int HARALICK_UNIFORMITY = 3; 
    public static final int HARALICK_MAXIMUM_PROBABILITY = 4; 
    public static final int HARALICK_ENTROPY = 5; 
    public static final int VARIANCE = 6; 
 
    public BufferedImage getProcessedImage(final int haralickType) { 
 
        BufferedImage image = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB); 
 
        double min = Double.MAX_VALUE; 
        double max = 0; 
 
        double[][] saved = new double[img.getWidth()][img.getHeight()]; 
 
        // run once to find min and max 
        int border = HARALICK_SIZE + 1; 
        for (int yPos = border; yPos < img.getHeight() - border; yPos++) { 
            for (int xPos = border; xPos < img.getWidth() - border; xPos++) { 
 
 
                double c = 0; 
 
                switch (haralickType) { 
                    case HARALICK_CONTRAST: 
                        c = getContrast(xPos, yPos); 
                        break; 
                    case HARALICK_DISSIMILARITY: 
                        c = getDissimilarity(xPos, yPos); 
                        break; 
                    case HARALICK_UNIFORMITY: 
                        c = getUniformity(xPos, yPos); 
                        break; 
                    case HARALICK_ENTROPY: 
                        c = getEntropy(xPos, yPos); 
                        break; 
                    case HARALICK_MAXIMUM_PROBABILITY: 
                        c = getMaximumProbability(xPos, yPos); 
                        break; 
                    case VARIANCE: 
                        c = get3x3Variance(xPos, yPos); 
                } 
 
 
                if (c < min) min = c; 
                else if (c > max) max = c; 
 
                saved[xPos][yPos] = c; 
 
            } 
        } 
 
        // find the difference between min and max. This will be the scaling factor. 
        double range = max - min; 
 
        for (int yPos = border; yPos < img.getHeight() - border; yPos++) { 
            for (int xPos = border; xPos < img.getWidth() - border; xPos++) { 
 
                // histogram stretch to emphasize contrast 
                int value = (int) (((saved[xPos][yPos] - min) / range) * 255); 
 
                // drawPixels on the image 
                image.setRGB(xPos, yPos, new Color(value, value, value).getRGB()); 
 
            } 
        } 
 
        return image; 
 
    } 
 
    public static final int HARALICK_SIZE = 8; 
 
    /** 
     * Gets the contrast from the gray level co-occurence matrix 
     */ 
    public double getContrast(int x, int y) { 
 
        double[][] glcm = getHorizontalGLCM(x, y, HARALICK_SIZE); 
 
        double total = 0; 
 
        for (int dy = 0; dy < GLCM_SIZE; dy++) 
            for (int dx = 0; dx < GLCM_SIZE; dx++) { 
                total += glcm[dx][dy] * ((dx - dy) ^ 2); 
            } 
 
        return total; 
 
    } 
 
    /** 
     * Gets the dissimilarity. Similar to contrast but uses 
     * a non exponential scale. 
     */ 
    public double getDissimilarity(int x, int y) { 
 
        double[][] glcm = getHorizontalGLCM(x, y, HARALICK_SIZE); 
 
        double total = 0; 
 
        for (int dy = 0; dy < GLCM_SIZE; dy++) 
            for (int dx = 0; dx < GLCM_SIZE; dx++) { 
                total += glcm[dx][dy] * (Math.abs(dx - dy)); 
            } 
 
        return total; 
 
    } 
 
    /** 
     * Gets the uniformity 
     */ 
    public double getUniformity(int x, int y) { 
 
        double[][] glcm = getHorizontalGLCM(x, y, HARALICK_SIZE); 
 
        double total = 0; 
 
        for (int dy = 0; dy < GLCM_SIZE; dy++) 
            for (int dx = 0; dx < GLCM_SIZE; dx++) { 
                total += glcm[dx][dy] * glcm[dx][dy]; 
            } 
 
        return total; 
 
    } 
 
    /** 
     * Gets the entropy 
     */ 
    public double getEntropy(int x, int y) { 
 
        double[][] glcm = getHorizontalGLCM(x, y, HARALICK_SIZE); 
 
        double total = 0; 
 
        for (int dy = 0; dy < GLCM_SIZE; dy++) 
            for (int dx = 0; dx < GLCM_SIZE; dx++) { 
                total += glcm[dx][dy] * (-Math.log1p(glcm[dx][dy])); 
            } 
 
        return total; 
 
    } 
 
    /** 
     * Gets the maximum probability 
     */ 
    public double getMaximumProbability(int x, int y) { 
 
        double[][] glcm = getHorizontalGLCM(x, y, HARALICK_SIZE); 
 
        double max = 0; 
 
        for (int dy = 0; dy < GLCM_SIZE; dy++) 
            for (int dx = 0; dx < GLCM_SIZE; dx++) { 
                if (glcm[dx][dy] > max) { 
                    max = glcm[dx][dy]; 
                } 
            } 
 
        return max; 
 
    } 
 
    final int GLCM_SIZE = 16; 
    final int GLCM_QUANTISATION = 256 / GLCM_SIZE; 
 
    public int quantize(int value) { 
        if (value > 240) value = 240; 
        value -= 210; 
        if (value < 0) value = 0; 
        return value / GLCM_QUANTISATION; 
    } 
 
 
    /** 
     * Produces a Gray Level Co-occurence Matrix for a window 
     * centered at (x,y) with a width and height of twice half size + 1 
     * The GLCM is based on a 4 bit image quantization resulting in a 
     * 16x16 array of 256 co-occurence values. 
     */ 
    public double[][] getHorizontalGLCM(final int x, final int y, final int halfSize) { 
        double[][] glcm = new double[GLCM_SIZE][GLCM_SIZE]; 
        //int count = 0; 
        for (int dy = -halfSize; dy <= halfSize; dy++) { 
            for (int dx = -halfSize; dx <= halfSize; dx++) { 
                int xPos = x + dx; 
                int yPos = y + dy; 
                // find the reference pixel 
                int referenceValue = quantize(getGreyValue(xPos, yPos)); 
                // and its neighbour (to the immediate right) 
                int neighbourValue = quantize(getGreyValue(xPos + 1, yPos)); 
                // increment the glcm 
                glcm[referenceValue][neighbourValue]++; 
                // increment the opposite side too (for symmetry) 
                glcm[neighbourValue][referenceValue]++; 
                //count+= 2; 
            } 
        } 
 
        // normalise values 
/*        for (int i = 0; i < GLCM_SIZE; i++) { 
            for (int j = 0; j < GLCM_SIZE; j++) { 
                glcm[i][j] /= count; 
            } 
        }*/ 
 
        return glcm; 
 
    } 
 
    public FastStatistics get3x3Stats(int x, int y, int channel) { 
        final int size = 1; 
        FastStatistics solver = new FastStatistics(); 
 
        if (y - size < 0) return solver; 
        if (y + size > (getHeight() - 1)) return solver; 
        if (x - size < 0) return solver; 
        if (x + size > (getWidth() - 1)) return solver; 
 
        for (int dy = -size; dy <= size; dy++) { 
            int newY = y + dy; 
            for (int dx = -size; dx <= size; dx++) { 
                int newX = x + dx; 
                switch (channel) { 
                    case ColourChannels.RED: 
                        solver.addData(getRed(newX, newY)); 
                        break; 
                    case ColourChannels.GREEN: 
                        solver.addData(getGreen(newX, newY)); 
                        break; 
                    case ColourChannels.BLUE: 
                        solver.addData(getBlue(newX, newY)); 
                        break; 
                    case ColourChannels.HUE: 
                        solver.addData(getHue(newX, newY)); 
                        break; 
                    case ColourChannels.SATURATION: 
                        solver.addData(getSaturation(newX, newY)); 
                        break; 
                    case ColourChannels.LIGHTNESS: 
                        solver.addData(getLightness(newX, newY)); 
                        break; 
                    case ColourChannels.GREYSCALE: 
                        solver.addData(getGreyValue(newX, newY)); 
                        break; 
                } 
            } 
        } 
        return solver; 
    } 
 
    /** 
     * Speed improvements, 19th Feb 2008 
     * Now runs 39% faster. 
     */ 
    public int getConvolved(int x, int y, ConvolutionMatrix matrix) { 
        double sum = 0; 
        int size = matrix.getWidth() / 2; 
 
        if (y - size < 0) return 0; 
        if (y + size > (getHeight() - 1)) return 0; 
        if (x - size < 0) return 0; 
        if (x + size > (getWidth() - 1)) return 0; 
 
        for (int dy = -size; dy <= size; dy++) { 
            int newY = y + dy; 
            for (int dx = -size; dx <= size; dx++) { 
                int newX = x + dx; 
                sum += getGreyValue(newX, newY) * matrix.getWeight(dx + size, dy + size); 
            } 
        } 
 
        return (int) (sum / matrix.getTotal()); 
    } 
 
    public int[] getPrecomputedColours() { 
        // Precache the colours 
        int[] colours = new int[256]; 
        for (int i = 0; i <= 255; i++) { 
            colours[i] = new Color(i, i, i).getRGB(); 
        } 
        return colours; 
    } 
 
    /** 
     * Speed improvements, 19th Feb 2008 
     * Now runs 39% faster. 
     */ 
    public BufferedImage getConvolved(ConvolutionMatrix matrix) { 
 
        BufferedImage image = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB); 
 
        int[] colours = getPrecomputedColours(); 
 
        for (int yPos = 0; yPos < img.getHeight(); yPos++) { 
            for (int xPos = 0; xPos < img.getWidth(); xPos++) { 
 
                int c = getConvolved(xPos, yPos, matrix); 
                if (c > 255) c = 255; 
                if (c < 0) c = 0; 
                if (c != 0) image.setRGB(xPos, yPos, colours[c]); 
 
            } 
        } 
 
        return image; 
 
    } 
 
 
    public void setRGB(int x, int y, int colour) { 
        img.setRGB(x, y, colour); 
        // todo - this is wrong!! 
        if (greyColourCache != null) { 
            greyColourCache[y][x] = colour; 
        } 
    } 
 
     
 
    public void saveAs(String filename) throws Exception { 
        saveAs(new File(filename)); 
    } 
 
    public void saveAs(File imageFile) throws Exception { 
        try { 
            String type = "bmp"; 
            if (imageFile.getName().endsWith(".png")) { 
                type = "png"; 
            } 
            if (imageFile.getName().endsWith(".jpg")) { 
                type = "jpg"; 
            } 
            // Use the Java ImageIO library to shave the file as a PNG image 
            javax.imageio.ImageIO.write(img, type, imageFile); 
        } catch (Exception e) { 
            e.printStackTrace(); 
        } 
    } 
 
    public int getWidth() { 
        return img.getWidth(); 
    } 
 
    public int getHeight() { 
        return img.getHeight(); 
    } 
 
    public String getFilename() { 
        return getFile().getName(); 
    } 
 
}