001/* 002Copyright 2006 Jerry Huxtable 003 004Licensed under the Apache License, Version 2.0 (the "License"); 005you may not use this file except in compliance with the License. 006You may obtain a copy of the License at 007 008 http://www.apache.org/licenses/LICENSE-2.0 009 010Unless required by applicable law or agreed to in writing, software 011distributed under the License is distributed on an "AS IS" BASIS, 012WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013See the License for the specific language governing permissions and 014limitations under the License. 015*/ 016 017package com.jhlabs.image; 018 019import java.awt.*; 020import java.awt.image.*; 021 022/** 023 * An abstract superclass for filters which distort images in some way. The subclass only needs to override 024 * two methods to provide the mapping between source and destination pixels. 025 */ 026public abstract class TransformFilter extends AbstractBufferedImageOp { 027 028 /** 029 * Treat pixels off the edge as zero. 030 */ 031 public final static int ZERO = 0; 032 033 /** 034 * Clamp pixels to the image edges. 035 */ 036 public final static int CLAMP = 1; 037 038 /** 039 * Wrap pixels off the edge onto the oppsoite edge. 040 */ 041 public final static int WRAP = 2; 042 043 /** 044 * Clamp pixels RGB to the image edges, but zero the alpha. This prevents gray borders on your image. 045 */ 046 public final static int RGB_CLAMP = 3; 047 048 /** 049 * Use nearest-neighbout interpolation. 050 */ 051 public final static int NEAREST_NEIGHBOUR = 0; 052 053 /** 054 * Use bilinear interpolation. 055 */ 056 public final static int BILINEAR = 1; 057 058 /** 059 * The action to take for pixels off the image edge. 060 */ 061 protected int edgeAction = RGB_CLAMP; 062 063 /** 064 * The type of interpolation to use. 065 */ 066 protected int interpolation = BILINEAR; 067 068 /** 069 * The output image rectangle. 070 */ 071 protected Rectangle transformedSpace; 072 073 /** 074 * The input image rectangle. 075 */ 076 protected Rectangle originalSpace; 077 078 /** 079 * Set the action to perform for pixels off the edge of the image. 080 * @param edgeAction one of ZERO, CLAMP or WRAP 081 * @see #getEdgeAction 082 */ 083 public void setEdgeAction(int edgeAction) { 084 this.edgeAction = edgeAction; 085 } 086 087 /** 088 * Get the action to perform for pixels off the edge of the image. 089 * @return one of ZERO, CLAMP or WRAP 090 * @see #setEdgeAction 091 */ 092 public int getEdgeAction() { 093 return edgeAction; 094 } 095 096 /** 097 * Set the type of interpolation to perform. 098 * @param interpolation one of NEAREST_NEIGHBOUR or BILINEAR 099 * @see #getInterpolation 100 */ 101 public void setInterpolation(int interpolation) { 102 this.interpolation = interpolation; 103 } 104 105 /** 106 * Get the type of interpolation to perform. 107 * @return one of NEAREST_NEIGHBOUR or BILINEAR 108 * @see #setInterpolation 109 */ 110 public int getInterpolation() { 111 return interpolation; 112 } 113 114 /** 115 * Inverse transform a point. This method needs to be overriden by all subclasses. 116 * @param x the X position of the pixel in the output image 117 * @param y the Y position of the pixel in the output image 118 * @param out the position of the pixel in the input image 119 */ 120 protected abstract void transformInverse(int x, int y, float[] out); 121 122 /** 123 * Forward transform a rectangle. Used to determine the size of the output image. 124 * @param rect the rectangle to transform 125 */ 126 protected void transformSpace(Rectangle rect) { 127 } 128 129 public BufferedImage filter( BufferedImage src, BufferedImage dst ) { 130 int width = src.getWidth(); 131 int height = src.getHeight(); 132 int type = src.getType(); 133 WritableRaster srcRaster = src.getRaster(); 134 135 originalSpace = new Rectangle(0, 0, width, height); 136 transformedSpace = new Rectangle(0, 0, width, height); 137 transformSpace(transformedSpace); 138 139 if ( dst == null ) { 140 ColorModel dstCM = src.getColorModel(); 141 dst = new BufferedImage(dstCM, dstCM.createCompatibleWritableRaster(transformedSpace.width, transformedSpace.height), dstCM.isAlphaPremultiplied(), null); 142 } 143 WritableRaster dstRaster = dst.getRaster(); 144 145 int[] inPixels = getRGB( src, 0, 0, width, height, null ); 146 147 if ( interpolation == NEAREST_NEIGHBOUR ) 148 return filterPixelsNN( dst, width, height, inPixels, transformedSpace ); 149 150 int srcWidth = width; 151 int srcHeight = height; 152 int srcWidth1 = width-1; 153 int srcHeight1 = height-1; 154 int outWidth = transformedSpace.width; 155 int outHeight = transformedSpace.height; 156 int outX, outY; 157 int index = 0; 158 int[] outPixels = new int[outWidth]; 159 160 outX = transformedSpace.x; 161 outY = transformedSpace.y; 162 float[] out = new float[2]; 163 164 for (int y = 0; y < outHeight; y++) { 165 for (int x = 0; x < outWidth; x++) { 166 transformInverse(outX+x, outY+y, out); 167 int srcX = (int)Math.floor( out[0] ); 168 int srcY = (int)Math.floor( out[1] ); 169 float xWeight = out[0]-srcX; 170 float yWeight = out[1]-srcY; 171 int nw, ne, sw, se; 172 173 if ( srcX >= 0 && srcX < srcWidth1 && srcY >= 0 && srcY < srcHeight1) { 174 // Easy case, all corners are in the image 175 int i = srcWidth*srcY + srcX; 176 nw = inPixels[i]; 177 ne = inPixels[i+1]; 178 sw = inPixels[i+srcWidth]; 179 se = inPixels[i+srcWidth+1]; 180 } else { 181 // Some of the corners are off the image 182 nw = getPixel( inPixels, srcX, srcY, srcWidth, srcHeight ); 183 ne = getPixel( inPixels, srcX+1, srcY, srcWidth, srcHeight ); 184 sw = getPixel( inPixels, srcX, srcY+1, srcWidth, srcHeight ); 185 se = getPixel( inPixels, srcX+1, srcY+1, srcWidth, srcHeight ); 186 } 187 outPixels[x] = ImageMath.bilinearInterpolate(xWeight, yWeight, nw, ne, sw, se); 188 } 189 setRGB( dst, 0, y, transformedSpace.width, 1, outPixels ); 190 } 191 return dst; 192 } 193 194 final private int getPixel( int[] pixels, int x, int y, int width, int height ) { 195 if (x < 0 || x >= width || y < 0 || y >= height) { 196 switch (edgeAction) { 197 case ZERO: 198 default: 199 return 0; 200 case WRAP: 201 return pixels[(ImageMath.mod(y, height) * width) + ImageMath.mod(x, width)]; 202 case CLAMP: 203 return pixels[(ImageMath.clamp(y, 0, height-1) * width) + ImageMath.clamp(x, 0, width-1)]; 204 case RGB_CLAMP: 205 return pixels[(ImageMath.clamp(y, 0, height-1) * width) + ImageMath.clamp(x, 0, width-1)] & 0x00ffffff; 206 } 207 } 208 return pixels[ y*width+x ]; 209 } 210 211 protected BufferedImage filterPixelsNN( BufferedImage dst, int width, int height, int[] inPixels, Rectangle transformedSpace ) { 212 int srcWidth = width; 213 int srcHeight = height; 214 int outWidth = transformedSpace.width; 215 int outHeight = transformedSpace.height; 216 int outX, outY, srcX, srcY; 217 int[] outPixels = new int[outWidth]; 218 219 outX = transformedSpace.x; 220 outY = transformedSpace.y; 221 int[] rgb = new int[4]; 222 float[] out = new float[2]; 223 224 for (int y = 0; y < outHeight; y++) { 225 for (int x = 0; x < outWidth; x++) { 226 transformInverse(outX+x, outY+y, out); 227 srcX = (int)out[0]; 228 srcY = (int)out[1]; 229 // int casting rounds towards zero, so we check out[0] < 0, not srcX < 0 230 if (out[0] < 0 || srcX >= srcWidth || out[1] < 0 || srcY >= srcHeight) { 231 int p; 232 switch (edgeAction) { 233 case ZERO: 234 default: 235 p = 0; 236 break; 237 case WRAP: 238 p = inPixels[(ImageMath.mod(srcY, srcHeight) * srcWidth) + ImageMath.mod(srcX, srcWidth)]; 239 break; 240 case CLAMP: 241 p = inPixels[(ImageMath.clamp(srcY, 0, srcHeight-1) * srcWidth) + ImageMath.clamp(srcX, 0, srcWidth-1)]; 242 break; 243 case RGB_CLAMP: 244 p = inPixels[(ImageMath.clamp(srcY, 0, srcHeight-1) * srcWidth) + ImageMath.clamp(srcX, 0, srcWidth-1)] & 0x00ffffff; 245 } 246 outPixels[x] = p; 247 } else { 248 int i = srcWidth*srcY + srcX; 249 rgb[0] = inPixels[i]; 250 outPixels[x] = inPixels[i]; 251 } 252 } 253 setRGB( dst, 0, y, transformedSpace.width, 1, outPixels ); 254 } 255 return dst; 256 } 257 258} 259