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