content: /*
  ** Copyright 2005 Huxtable.com. All rights reserved.
  */
  
  package com.jhlabs.image;
  
  import java.awt.*;
  import java.awt.image.*;
  import java.awt.geom.*;
  
  /**
   * A filter which applies a convolution kernel to an image.
   * @author Jerry Huxtable
   */
  public class ConvolveFilter extends AbstractBufferedImageOp {
  
  	static final long serialVersionUID = 2239251672685254626L;
  	
  	public static int ZERO_EDGES = 0;
  	public static int CLAMP_EDGES = 1;
  	public static int WRAP_EDGES = 2;
  
  	protected Kernel kernel = null;
  	public boolean alpha = true;
  	private int edgeAction = CLAMP_EDGES;
  
  	/**
  	 * Construct a filter with a null kernel. This is only useful if you're going to change the kernel later on.
  	 */
  	public ConvolveFilter() {
  		this(new float[9]);
  	}
  
  	/**
  	 * Construct a filter with the given 3x3 kernel.
  	 * @param matrix an array of 9 floats containing the kernel
  	 */
  	public ConvolveFilter(float[] matrix) {
  		this(new Kernel(3, 3, matrix));
  	}
  	
  	/**
  	 * Construct a filter with the given kernel.
  	 * @param rows	the number of rows in the kernel
  	 * @param cols	the number of columns in the kernel
  	 * @param matrix	an array of rows*cols floats containing the kernel
  	 */
  	public ConvolveFilter(int rows, int cols, float[] matrix) {
  		this(new Kernel(cols, rows, matrix));
  	}
  	
  	/**
  	 * Construct a filter with the given 3x3 kernel.
  	 * @param matrix an array of 9 floats containing the kernel
  	 */
  	public ConvolveFilter(Kernel kernel) {
  		this.kernel = kernel;	
  	}
  
  	public void setKernel(Kernel kernel) {
  		this.kernel = kernel;
  	}
  
  	public Kernel getKernel() {
  		return kernel;
  	}
  
  	public void setEdgeAction(int edgeAction) {
  		this.edgeAction = edgeAction;
  	}
  
  	public int getEdgeAction() {
  		return edgeAction;
  	}
  
      public BufferedImage filter( BufferedImage src, BufferedImage dst ) {
          int width = src.getWidth();
          int height = src.getHeight();
  
          if ( dst == null )
              dst = createCompatibleDestImage( src, null );
  
          int[] inPixels = new int[width*height];
          int[] outPixels = new int[width*height];
          getRGB( src, 0, 0, width, height, inPixels );
  
  		convolve(kernel, inPixels, outPixels, width, height, alpha, edgeAction);
  
          setRGB( dst, 0, 0, width, height, outPixels );
          return dst;
      }
  
      public BufferedImage createCompatibleDestImage(BufferedImage src, ColorModel dstCM) {
          if ( dstCM == null )
              dstCM = src.getColorModel();
          return new BufferedImage(dstCM, dstCM.createCompatibleWritableRaster(src.getWidth(), src.getHeight()), dstCM.isAlphaPremultiplied(), null);
      }
      
      public Rectangle2D getBounds2D( BufferedImage src ) {
          return new Rectangle(0, 0, src.getWidth(), src.getHeight());
      }
      
      public Point2D getPoint2D( Point2D srcPt, Point2D dstPt ) {
          if ( dstPt == null )
              dstPt = new Point2D.Double();
          dstPt.setLocation( srcPt.getX(), srcPt.getY() );
          return dstPt;
      }
  
      public RenderingHints getRenderingHints() {
          return null;
      }
  
  	public static void convolve(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, int edgeAction) {
  		convolve(kernel, inPixels, outPixels, width, height, true, edgeAction);
  	}
  	
  	public static void convolve(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, boolean alpha, int edgeAction) {
  		if (kernel.getHeight() == 1)
  			convolveH(kernel, inPixels, outPixels, width, height, alpha, edgeAction);
  		else if (kernel.getWidth() == 1)
  			convolveV(kernel, inPixels, outPixels, width, height, alpha, edgeAction);
  		else
  			convolveHV(kernel, inPixels, outPixels, width, height, alpha, edgeAction);
  	}
  	
  	/**
  	 * Convolve with a 2D kernel
  	 */
  	public static void convolveHV(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, boolean alpha, int edgeAction) {
  		int index = 0;
  		float[] matrix = kernel.getKernelData( null );
  		int rows = kernel.getHeight();
  		int cols = kernel.getWidth();
  		int rows2 = rows/2;
  		int cols2 = cols/2;
  
  		for (int y = 0; y < height; y++) {
  			for (int x = 0; x < width; x++) {
  				float r = 0, g = 0, b = 0, a = 0;
  
  				for (int row = -rows2; row <= rows2; row++) {
  					int iy = y+row;
  					int ioffset;
  					if (0 <= iy && iy < height)
  						ioffset = iy*width;
  					else if ( edgeAction == CLAMP_EDGES )
  						ioffset = y*width;
  					else if ( edgeAction == WRAP_EDGES )
  						ioffset = ((iy+height) % height) * width;
  					else
  						continue;
  					int moffset = cols*(row+rows2)+cols2;
  					for (int col = -cols2; col <= cols2; col++) {
  						float f = matrix[moffset+col];
  
  						if (f != 0) {
  							int ix = x+col;
  							if (!(0 <= ix && ix < width)) {
  								if ( edgeAction == CLAMP_EDGES )
  									ix = x;
  								else if ( edgeAction == WRAP_EDGES )
  									ix = (x+width) % width;
  								else
  									continue;
  							}
  							int rgb = inPixels[ioffset+ix];
  							a += f * ((rgb >> 24) & 0xff);
  							r += f * ((rgb >> 16) & 0xff);
  							g += f * ((rgb >> 8) & 0xff);
  							b += f * (rgb & 0xff);
  						}
  					}
  				}
  				int ia = alpha ? PixelUtils.clamp((int)(a+0.5)) : 0xff;
  				int ir = PixelUtils.clamp((int)(r+0.5));
  				int ig = PixelUtils.clamp((int)(g+0.5));
  				int ib = PixelUtils.clamp((int)(b+0.5));
  				outPixels[index++] = (ia << 24) | (ir << 16) | (ig << 8) | ib;
  			}
  		}
  	}
  
  	/**
  	 * Convolve with a kernel consisting of one row
  	 */
  	public static void convolveH(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, boolean alpha, int edgeAction) {
  		int index = 0;
  		float[] matrix = kernel.getKernelData( null );
  		int cols = kernel.getWidth();
  		int cols2 = cols/2;
  
  		for (int y = 0; y < height; y++) {
  			int ioffset = y*width;
  			for (int x = 0; x < width; x++) {
  				float r = 0, g = 0, b = 0, a = 0;
  				int moffset = cols2;
  				for (int col = -cols2; col <= cols2; col++) {
  					float f = matrix[moffset+col];
  
  					if (f != 0) {
  						int ix = x+col;
  						if ( ix < 0 ) {
  							if ( edgeAction == CLAMP_EDGES )
  								ix = 0;
  							else if ( edgeAction == WRAP_EDGES )
  								ix = (x+width) % width;
  						} else if ( ix >= width) {
  							if ( edgeAction == CLAMP_EDGES )
  								ix = width-1;
  							else if ( edgeAction == WRAP_EDGES )
  								ix = (x+width) % width;
  						}
  						int rgb = inPixels[ioffset+ix];
  						a += f * ((rgb >> 24) & 0xff);
  						r += f * ((rgb >> 16) & 0xff);
  						g += f * ((rgb >> 8) & 0xff);
  						b += f * (rgb & 0xff);
  					}
  				}
  				int ia = alpha ? PixelUtils.clamp((int)(a+0.5)) : 0xff;
  				int ir = PixelUtils.clamp((int)(r+0.5));
  				int ig = PixelUtils.clamp((int)(g+0.5));
  				int ib = PixelUtils.clamp((int)(b+0.5));
  				outPixels[index++] = (ia << 24) | (ir << 16) | (ig << 8) | ib;
  			}
  		}
  	}
  
  	/**
  	 * Convolve with a kernel consisting of one column
  	 */
  	public static void convolveV(Kernel kernel, int[] inPixels, int[] outPixels, int width, int height, boolean alpha, int edgeAction) {
  		int index = 0;
  		float[] matrix = kernel.getKernelData( null );
  		int rows = kernel.getHeight();
  		int rows2 = rows/2;
  
  		for (int y = 0; y < height; y++) {
  			for (int x = 0; x < width; x++) {
  				float r = 0, g = 0, b = 0, a = 0;
  
  				for (int row = -rows2; row <= rows2; row++) {
  					int iy = y+row;
  					int ioffset;
  					if ( iy < 0 ) {
  						if ( edgeAction == CLAMP_EDGES )
  							ioffset = 0;
  						else if ( edgeAction == WRAP_EDGES )
  							ioffset = ((y+height) % height)*width;
  						else
  							ioffset = iy*width;
  					} else if ( iy >= height) {
  						if ( edgeAction == CLAMP_EDGES )
  							ioffset = (height-1)*width;
  						else if ( edgeAction == WRAP_EDGES )
  							ioffset = ((y+height) % height)*width;
  						else
  							ioffset = iy*width;
  					} else
  						ioffset = iy*width;
  
  					float f = matrix[row+rows2];
  
  					if (f != 0) {
  						int rgb = inPixels[ioffset+x];
  						a += f * ((rgb >> 24) & 0xff);
  						r += f * ((rgb >> 16) & 0xff);
  						g += f * ((rgb >> 8) & 0xff);
  						b += f * (rgb & 0xff);
  					}
  				}
  				int ia = alpha ? PixelUtils.clamp((int)(a+0.5)) : 0xff;
  				int ir = PixelUtils.clamp((int)(r+0.5));
  				int ig = PixelUtils.clamp((int)(g+0.5));
  				int ib = PixelUtils.clamp((int)(b+0.5));
  				outPixels[index++] = (ia << 24) | (ir << 16) | (ig << 8) | ib;
  			}
  		}
  	}
  
  	public String toString() {
  		return "Blur/Convolve...";
  	}
  }