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