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 import java.awt.image.WritableRaster; 021 022 import railo.runtime.engine.ThreadLocalPageContext; 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 * A page curl effect. 032 */ 033 public final class CurlFilter extends TransformFilter implements DynFiltering { 034 035 private float angle = 0; 036 private float transition = 0.0f; 037 038 private float width; 039 private float height; 040 private float radius; 041 042 /** 043 * Construct a CurlFilter with no distortion. 044 */ 045 public CurlFilter() { 046 super(ConvolveFilter.ZERO_EDGES ); 047 } 048 049 public void setTransition( float transition ) { 050 this.transition = transition; 051 } 052 053 public float getTransition() { 054 return transition; 055 } 056 057 public void setAngle(float angle) { 058 this.angle = angle; 059 } 060 061 public float getAngle() { 062 return angle; 063 } 064 065 public void setRadius( float radius ) { 066 this.radius = radius; 067 } 068 069 public float getRadius() { 070 return radius; 071 } 072 073 /* 074 public BufferedImage filter( BufferedImage src, BufferedImage dst ) { 075 this.width = src.getWidth(); 076 this.height = src.getHeight(); 077 return super.filter( src, dst ); 078 } 079 */ 080 081 static class Sampler { 082 private int edgeAction; 083 private int width, height; 084 private int[] inPixels; 085 086 public Sampler( BufferedImage image ) { 087 int width = image.getWidth(); 088 int height = image.getHeight(); 089 int type = image.getType(); 090 inPixels = ImageUtils.getRGB( image, 0, 0, width, height, null ); 091 } 092 093 public int sample( float x, float y ) { 094 int srcX = (int)Math.floor( x ); 095 int srcY = (int)Math.floor( y ); 096 float xWeight = x-srcX; 097 float yWeight = y-srcY; 098 int nw, ne, sw, se; 099 100 if ( srcX >= 0 && srcX < width-1 && srcY >= 0 && srcY < height-1) { 101 // Easy case, all corners are in the image 102 int i = width*srcY + srcX; 103 nw = inPixels[i]; 104 ne = inPixels[i+1]; 105 sw = inPixels[i+width]; 106 se = inPixels[i+width+1]; 107 } else { 108 // Some of the corners are off the image 109 nw = getPixel( inPixels, srcX, srcY, width, height ); 110 ne = getPixel( inPixels, srcX+1, srcY, width, height ); 111 sw = getPixel( inPixels, srcX, srcY+1, width, height ); 112 se = getPixel( inPixels, srcX+1, srcY+1, width, height ); 113 } 114 return ImageMath.bilinearInterpolate(xWeight, yWeight, nw, ne, sw, se); 115 } 116 117 final private int getPixel( int[] pixels, int x, int y, int width, int height ) { 118 if (x < 0 || x >= width || y < 0 || y >= height) { 119 switch (edgeAction) { 120 case ConvolveFilter.ZERO_EDGES: 121 default: 122 return 0; 123 case ConvolveFilter.WRAP_EDGES: 124 return pixels[(ImageMath.mod(y, height) * width) + ImageMath.mod(x, width)]; 125 case ConvolveFilter.CLAMP_EDGES: 126 return pixels[(ImageMath.clamp(y, 0, height-1) * width) + ImageMath.clamp(x, 0, width-1)]; 127 } 128 } 129 return pixels[ y*width+x ]; 130 } 131 } 132 133 public BufferedImage filter( BufferedImage src, BufferedImage dst ) { 134 int width = src.getWidth(); 135 int height = src.getHeight(); 136 this.width = src.getWidth(); 137 this.height = src.getHeight(); 138 int type = src.getType(); 139 140 originalSpace = new Rectangle(0, 0, width, height); 141 transformedSpace = new Rectangle(0, 0, width, height); 142 transformSpace(transformedSpace); 143 144 if ( dst == null ) { 145 ColorModel dstCM = src.getColorModel(); 146 dst = new BufferedImage(dstCM, dstCM.createCompatibleWritableRaster(transformedSpace.width, transformedSpace.height), dstCM.isAlphaPremultiplied(), null); 147 } 148 WritableRaster dstRaster = 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":"")+" ["+List.arrayToList(parameters.keysAsString(),", ")+"] "+(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 }