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.ColorModel; 020 021 import railo.runtime.engine.ThreadLocalPageContext; 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 * A page curl effect. 031 */ 032 public final class CurlFilter extends TransformFilter implements DynFiltering { 033 034 private float angle = 0; 035 private float transition = 0.0f; 036 037 private float width; 038 private float height; 039 private float radius; 040 041 /** 042 * Construct a CurlFilter with no distortion. 043 */ 044 public CurlFilter() { 045 super(ConvolveFilter.ZERO_EDGES ); 046 } 047 048 public void setTransition( float transition ) { 049 this.transition = transition; 050 } 051 052 public float getTransition() { 053 return transition; 054 } 055 056 public void setAngle(float angle) { 057 this.angle = angle; 058 } 059 060 public float getAngle() { 061 return angle; 062 } 063 064 public void setRadius( float radius ) { 065 this.radius = radius; 066 } 067 068 public float getRadius() { 069 return radius; 070 } 071 072 /* 073 public BufferedImage filter( BufferedImage src, BufferedImage dst ) { 074 this.width = src.getWidth(); 075 this.height = src.getHeight(); 076 return super.filter( src, dst ); 077 } 078 */ 079 080 static class Sampler { 081 private int edgeAction; 082 private int width, height; 083 private int[] inPixels; 084 085 public Sampler( BufferedImage image ) { 086 int width = image.getWidth(); 087 int height = image.getHeight(); 088 //int type = image.getType(); 089 inPixels = ImageUtils.getRGB( image, 0, 0, width, height, null ); 090 } 091 092 public int sample( float x, float y ) { 093 int srcX = (int)Math.floor( x ); 094 int srcY = (int)Math.floor( y ); 095 float xWeight = x-srcX; 096 float yWeight = y-srcY; 097 int nw, ne, sw, se; 098 099 if ( srcX >= 0 && srcX < width-1 && srcY >= 0 && srcY < height-1) { 100 // Easy case, all corners are in the image 101 int i = width*srcY + srcX; 102 nw = inPixels[i]; 103 ne = inPixels[i+1]; 104 sw = inPixels[i+width]; 105 se = inPixels[i+width+1]; 106 } else { 107 // Some of the corners are off the image 108 nw = getPixel( inPixels, srcX, srcY, width, height ); 109 ne = getPixel( inPixels, srcX+1, srcY, width, height ); 110 sw = getPixel( inPixels, srcX, srcY+1, width, height ); 111 se = getPixel( inPixels, srcX+1, srcY+1, width, height ); 112 } 113 return ImageMath.bilinearInterpolate(xWeight, yWeight, nw, ne, sw, se); 114 } 115 116 final private int getPixel( int[] pixels, int x, int y, int width, int height ) { 117 if (x < 0 || x >= width || y < 0 || y >= height) { 118 switch (edgeAction) { 119 case ConvolveFilter.ZERO_EDGES: 120 default: 121 return 0; 122 case ConvolveFilter.WRAP_EDGES: 123 return pixels[(ImageMath.mod(y, height) * width) + ImageMath.mod(x, width)]; 124 case ConvolveFilter.CLAMP_EDGES: 125 return pixels[(ImageMath.clamp(y, 0, height-1) * width) + ImageMath.clamp(x, 0, width-1)]; 126 } 127 } 128 return pixels[ y*width+x ]; 129 } 130 } 131 132 public BufferedImage filter( BufferedImage src, BufferedImage dst ) { 133 int width = src.getWidth(); 134 int height = src.getHeight(); 135 this.width = src.getWidth(); 136 this.height = src.getHeight(); 137 //int type = src.getType(); 138 139 originalSpace = new Rectangle(0, 0, width, height); 140 transformedSpace = new Rectangle(0, 0, width, height); 141 transformSpace(transformedSpace); 142 143 if ( dst == null ) { 144 ColorModel dstCM = src.getColorModel(); 145 dst = new BufferedImage(dstCM, dstCM.createCompatibleWritableRaster(transformedSpace.width, transformedSpace.height), dstCM.isAlphaPremultiplied(), null); 146 } 147 //WritableRaster dstRaster = 148 dst.getRaster(); 149 150 int[] inPixels = getRGB( src, 0, 0, width, height, null ); 151 152 if ( interpolation == NEAREST_NEIGHBOUR ) 153 return filterPixelsNN( dst, width, height, inPixels, transformedSpace ); 154 155 int srcWidth = width; 156 int srcHeight = height; 157 int srcWidth1 = width-1; 158 int srcHeight1 = height-1; 159 int outWidth = transformedSpace.width; 160 int outHeight = transformedSpace.height; 161 int outX, outY; 162 //int index = 0; 163 int[] outPixels = new int[outWidth]; 164 165 outX = transformedSpace.x; 166 outY = transformedSpace.y; 167 float[] out = new float[4]; 168 169 for (int y = 0; y < outHeight; y++) { 170 for (int x = 0; x < outWidth; x++) { 171 transformInverse(outX+x, outY+y, out); 172 int srcX = (int)Math.floor( out[0] ); 173 int srcY = (int)Math.floor( out[1] ); 174 float xWeight = out[0]-srcX; 175 float yWeight = out[1]-srcY; 176 int nw, ne, sw, se; 177 178 if ( srcX >= 0 && srcX < srcWidth1 && srcY >= 0 && srcY < srcHeight1) { 179 // Easy case, all corners are in the image 180 int i = srcWidth*srcY + srcX; 181 nw = inPixels[i]; 182 ne = inPixels[i+1]; 183 sw = inPixels[i+srcWidth]; 184 se = inPixels[i+srcWidth+1]; 185 } else { 186 // Some of the corners are off the image 187 nw = getPixel( inPixels, srcX, srcY, srcWidth, srcHeight ); 188 ne = getPixel( inPixels, srcX+1, srcY, srcWidth, srcHeight ); 189 sw = getPixel( inPixels, srcX, srcY+1, srcWidth, srcHeight ); 190 se = getPixel( inPixels, srcX+1, srcY+1, srcWidth, srcHeight ); 191 } 192 int rgb = ImageMath.bilinearInterpolate(xWeight, yWeight, nw, ne, sw, se); 193 int r = (rgb >> 16) & 0xff; 194 int g = (rgb >> 8) & 0xff; 195 int b = rgb & 0xff; 196 float shade = out[2]; 197 r = (int)(r * shade); 198 g = (int)(g * shade); 199 b = (int)(b * shade); 200 rgb = (rgb & 0xff000000) | (r << 16) | (g << 8) | b; 201 if ( out[3] != 0 ) 202 outPixels[x] = PixelUtils.combinePixels( rgb, inPixels[srcWidth*y + x], PixelUtils.NORMAL ); 203 else 204 outPixels[x] = rgb; 205 } 206 setRGB( dst, 0, y, transformedSpace.width, 1, outPixels ); 207 } 208 return dst; 209 } 210 211 final private int getPixel( int[] pixels, int x, int y, int width, int height ) { 212 if (x < 0 || x >= width || y < 0 || y >= height) { 213 switch (edgeAction) { 214 case ConvolveFilter.ZERO_EDGES: 215 default: 216 return 0; 217 case ConvolveFilter.WRAP_EDGES: 218 return pixels[(ImageMath.mod(y, height) * width) + ImageMath.mod(x, width)]; 219 case ConvolveFilter.CLAMP_EDGES: 220 return pixels[(ImageMath.clamp(y, 0, height-1) * width) + ImageMath.clamp(x, 0, width-1)]; 221 } 222 } 223 return pixels[ y*width+x ]; 224 } 225 226 protected void transformInverse(int x, int y, float[] out) { 227 /*Fisheye 228 float mirrorDistance = width*centreX; 229 float mirrorRadius = width*centreY; 230 float cx = width*.5f; 231 float cy = height*.5f; 232 float dx = x-cx; 233 float dy = y-cy; 234 float r2 = dx*dx+dy*dy; 235 float r = (float)Math.sqrt( r2 ); 236 float phi = (float)(Math.PI*.5-Math.asin( Math.sqrt( mirrorRadius*mirrorRadius-r2 )/mirrorRadius )); 237 r = r > mirrorRadius ? width : mirrorDistance * (float)Math.tan( phi ); 238 phi = (float)Math.atan2( dy, dx ); 239 out[0] = cx + r*(float)Math.cos( phi ); 240 out[1] = cy + r*(float)Math.sin( phi ); 241 */ 242 float t = transition; 243 float px = x, py = y; 244 float s = (float)Math.sin( angle ); 245 float c = (float)Math.cos( angle ); 246 float tx = t*width; 247 tx = t * (float)Math.sqrt( width*width + height*height); 248 249 // Start from the correct corner according to the angle 250 float xoffset = c < 0 ? width : 0; 251 float yoffset = s < 0 ? height : 0; 252 253 // Transform into unrotated coordinates 254 px -= xoffset; 255 py -= yoffset; 256 257 float qx = px * c + py * s; 258 float qy = -px * s + py * c; 259 260 boolean outside = qx < tx; 261 boolean unfolded = qx > tx*2; 262 boolean oncurl = !(outside || unfolded); 263 264 qx = qx > tx*2 ? qx : 2*tx-qx; 265 266 // Transform back into rotated coordinates 267 px = qx * c - qy * s; 268 py = qx * s + qy * c; 269 px += xoffset; 270 py += yoffset; 271 272 // See if we're off the edge of the page 273 boolean offpage = px < 0 || py < 0 || px >= width || py >= height; 274 275 // If we're off the edge, but in the curl... 276 if ( offpage && oncurl ) { 277 px = x; 278 py = y; 279 } 280 281 // Shade the curl 282 float shade = !offpage && oncurl ? 1.9f * (1.0f-(float)Math.cos( Math.exp((qx-tx)/radius) )) : 0; 283 out[2] = 1-shade; 284 285 if ( outside ) { 286 out[0] = out[1] = -1; 287 } else { 288 out[0] = px; 289 out[1] = py; 290 } 291 292 out[3] = !offpage && oncurl ? 1 : 0; 293 } 294 295 public String toString() { 296 return "Distort/Curl..."; 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("Radius")))!=null)setRadius(ImageFilterUtil.toFloatValue(o,"Radius")); 302 if((o=parameters.removeEL(KeyImpl.init("Angle")))!=null)setAngle(ImageFilterUtil.toFloatValue(o,"Angle")); 303 if((o=parameters.removeEL(KeyImpl.init("Transition")))!=null)setTransition(ImageFilterUtil.toFloatValue(o,"Transition")); 304 if((o=parameters.removeEL(KeyImpl.init("EdgeAction")))!=null)setEdgeAction(ImageFilterUtil.toString(o,"EdgeAction")); 305 if((o=parameters.removeEL(KeyImpl.init("Interpolation")))!=null)setInterpolation(ImageFilterUtil.toString(o,"Interpolation")); 306 307 // check for arguments not supported 308 if(parameters.size()>0) { 309 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 [Radius, Angle, Transition, EdgeAction, Interpolation]"); 310 } 311 312 return filter(src, dst); 313 } 314 }