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