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