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.Kernel; 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.img.math.Function2D; 026 import railo.runtime.img.math.ImageFunction2D; 027 import railo.runtime.img.vecmath.Color4f; 028 import railo.runtime.img.vecmath.Vector3f; 029 import railo.runtime.type.KeyImpl; 030 import railo.runtime.type.Struct; 031 import railo.runtime.type.util.CollectionUtil; 032 033 public class ShadeFilter extends WholeImageFilter implements DynFiltering { 034 035 public final static int COLORS_FROM_IMAGE = 0; 036 public final static int COLORS_CONSTANT = 1; 037 038 public final static int BUMPS_FROM_IMAGE = 0; 039 public final static int BUMPS_FROM_IMAGE_ALPHA = 1; 040 public final static int BUMPS_FROM_MAP = 2; 041 public final static int BUMPS_FROM_BEVEL = 3; 042 043 private float bumpHeight; 044 private float bumpSoftness; 045 private float viewDistance = 10000.0f; 046 //private int colorSource = COLORS_FROM_IMAGE; 047 private int bumpSource = BUMPS_FROM_IMAGE; 048 private Function2D bumpFunction; 049 private BufferedImage environmentMap; 050 private int[] envPixels; 051 private int envWidth = 1, envHeight = 1; 052 //private Vector3f l; 053 private Vector3f v; 054 //private Vector3f n; 055 //private Color4f shadedColor; 056 //private Color4f diffuse_color; 057 //private Color4f specular_color; 058 private Vector3f tmpv, tmpv2; 059 060 public ShadeFilter() { 061 bumpHeight = 1.0f; 062 bumpSoftness = 5.0f; 063 //l = new Vector3f(); 064 v = new Vector3f(); 065 //n = new Vector3f(); 066 //shadedColor = new Color4f(); 067 //diffuse_color = new Color4f(); 068 //specular_color = new Color4f(); 069 tmpv = new Vector3f(); 070 tmpv2 = new Vector3f(); 071 } 072 073 public void setBumpFunction(Function2D bumpFunction) { 074 this.bumpFunction = bumpFunction; 075 } 076 077 public Function2D getBumpFunction() { 078 return bumpFunction; 079 } 080 081 public void setBumpHeight(float bumpHeight) { 082 this.bumpHeight = bumpHeight; 083 } 084 085 public float getBumpHeight() { 086 return bumpHeight; 087 } 088 089 public void setBumpSoftness(float bumpSoftness) { 090 this.bumpSoftness = bumpSoftness; 091 } 092 093 public float getBumpSoftness() { 094 return bumpSoftness; 095 } 096 097 public void setEnvironmentMap(BufferedImage environmentMap) { 098 this.environmentMap = environmentMap; 099 if (environmentMap != null) { 100 envWidth = environmentMap.getWidth(); 101 envHeight = environmentMap.getHeight(); 102 envPixels = getRGB( environmentMap, 0, 0, envWidth, envHeight, null ); 103 } else { 104 envWidth = envHeight = 1; 105 envPixels = null; 106 } 107 } 108 109 public BufferedImage getEnvironmentMap() { 110 return environmentMap; 111 } 112 113 public void setBumpSource(int bumpSource) { 114 this.bumpSource = bumpSource; 115 } 116 117 public int getBumpSource() { 118 return bumpSource; 119 } 120 121 protected final static float r255 = 1.0f/255.0f; 122 123 protected void setFromRGB( Color4f c, int argb ) { 124 c.set( ((argb >> 16) & 0xff) * r255, ((argb >> 8) & 0xff) * r255, (argb & 0xff) * r255, ((argb >> 24) & 0xff) * r255 ); 125 } 126 127 protected int[] filterPixels( int width, int height, int[] inPixels, Rectangle transformedSpace ) { 128 int index = 0; 129 int[] outPixels = new int[width * height]; 130 float width45 = Math.abs(6.0f * bumpHeight); 131 boolean invertBumps = bumpHeight < 0; 132 Vector3f position = new Vector3f(0.0f, 0.0f, 0.0f); 133 Vector3f viewpoint = new Vector3f(width / 2.0f, height / 2.0f, viewDistance); 134 Vector3f normal = new Vector3f(); 135 Color4f c = new Color4f(); 136 Function2D bump = bumpFunction; 137 138 if (bumpSource == BUMPS_FROM_IMAGE || bumpSource == BUMPS_FROM_IMAGE_ALPHA || bumpSource == BUMPS_FROM_MAP || bump == null) { 139 if ( bumpSoftness != 0 ) { 140 int bumpWidth = width; 141 int bumpHeight = height; 142 int[] bumpPixels = inPixels; 143 if ( bumpSource == BUMPS_FROM_MAP && bumpFunction instanceof ImageFunction2D ) { 144 ImageFunction2D if2d = (ImageFunction2D)bumpFunction; 145 bumpWidth = if2d.getWidth(); 146 bumpHeight = if2d.getHeight(); 147 bumpPixels = if2d.getPixels(); 148 } 149 Kernel kernel = GaussianFilter.makeKernel( bumpSoftness ); 150 int [] tmpPixels = new int[bumpWidth * bumpHeight]; 151 int [] softPixels = new int[bumpWidth * bumpHeight]; 152 GaussianFilter.convolveAndTranspose( kernel, bumpPixels, tmpPixels, bumpWidth, bumpHeight, true, false, false, ConvolveFilter.CLAMP_EDGES); 153 GaussianFilter.convolveAndTranspose( kernel, tmpPixels, softPixels, bumpHeight, bumpWidth, true, false, false, ConvolveFilter.CLAMP_EDGES); 154 bump = new ImageFunction2D(softPixels, bumpWidth, bumpHeight, ImageFunction2D.CLAMP, bumpSource == BUMPS_FROM_IMAGE_ALPHA); 155 } else 156 bump = new ImageFunction2D(inPixels, width, height, ImageFunction2D.CLAMP, bumpSource == BUMPS_FROM_IMAGE_ALPHA); 157 } 158 159 Vector3f v1 = new Vector3f(); 160 Vector3f v2 = new Vector3f(); 161 Vector3f n = new Vector3f(); 162 163 // Loop through each source pixel 164 for (int y = 0; y < height; y++) { 165 float ny = y; 166 position.y = y; 167 for (int x = 0; x < width; x++) { 168 float nx = x; 169 170 // Calculate the normal at this point 171 if (bumpSource != BUMPS_FROM_BEVEL) { 172 // Complicated and slower method 173 // Calculate four normals using the gradients in +/- X/Y directions 174 int count = 0; 175 normal.x = normal.y = normal.z = 0; 176 float m0 = width45*bump.evaluate(nx, ny); 177 float m1 = x > 0 ? width45*bump.evaluate(nx - 1.0f, ny)-m0 : -2; 178 float m2 = y > 0 ? width45*bump.evaluate(nx, ny - 1.0f)-m0 : -2; 179 float m3 = x < width-1 ? width45*bump.evaluate(nx + 1.0f, ny)-m0 : -2; 180 float m4 = y < height-1 ? width45*bump.evaluate(nx, ny + 1.0f)-m0 : -2; 181 182 if (m1 != -2 && m4 != -2) { 183 v1.x = -1.0f; v1.y = 0.0f; v1.z = m1; 184 v2.x = 0.0f; v2.y = 1.0f; v2.z = m4; 185 n.cross(v1, v2); 186 n.normalize(); 187 if (n.z < 0.0) 188 n.z = -n.z; 189 normal.add(n); 190 count++; 191 } 192 193 if (m1 != -2 && m2 != -2) { 194 v1.x = -1.0f; v1.y = 0.0f; v1.z = m1; 195 v2.x = 0.0f; v2.y = -1.0f; v2.z = m2; 196 n.cross(v1, v2); 197 n.normalize(); 198 if (n.z < 0.0) 199 n.z = -n.z; 200 normal.add(n); 201 count++; 202 } 203 204 if (m2 != -2 && m3 != -2) { 205 v1.x = 0.0f; v1.y = -1.0f; v1.z = m2; 206 v2.x = 1.0f; v2.y = 0.0f; v2.z = m3; 207 n.cross(v1, v2); 208 n.normalize(); 209 if (n.z < 0.0) 210 n.z = -n.z; 211 normal.add(n); 212 count++; 213 } 214 215 if (m3 != -2 && m4 != -2) { 216 v1.x = 1.0f; v1.y = 0.0f; v1.z = m3; 217 v2.x = 0.0f; v2.y = 1.0f; v2.z = m4; 218 n.cross(v1, v2); 219 n.normalize(); 220 if (n.z < 0.0) 221 n.z = -n.z; 222 normal.add(n); 223 count++; 224 } 225 226 // Average the four normals 227 normal.x /= count; 228 normal.y /= count; 229 normal.z /= count; 230 } 231 232 /* For testing - generate a sphere bump map 233 double dx = x-120; 234 double dy = y-80; 235 double r2 = dx*dx+dy*dy; 236 // double r = Math.sqrt( r2 ); 237 // double t = Math.atan2( dy, dx ); 238 if ( r2 < 80*80 ) { 239 double z = Math.sqrt( 80*80 - r2 ); 240 normal.x = (float)dx; 241 normal.y = (float)dy; 242 normal.z = (float)z; 243 normal.normalize(); 244 } else { 245 normal.x = 0; 246 normal.y = 0; 247 normal.z = 1; 248 } 249 */ 250 251 if (invertBumps) { 252 normal.x = -normal.x; 253 normal.y = -normal.y; 254 } 255 position.x = x; 256 257 if (normal.z >= 0) { 258 // Get the material colour at this point 259 if (environmentMap != null) { 260 //FIXME-too much normalizing going on here 261 tmpv2.set(viewpoint); 262 tmpv2.sub(position); 263 tmpv2.normalize(); 264 tmpv.set(normal); 265 tmpv.normalize(); 266 267 // Reflect 268 tmpv.scale( 2.0f*tmpv.dot(tmpv2) ); 269 tmpv.sub(v); 270 271 tmpv.normalize(); 272 setFromRGB(c, getEnvironmentMapP(normal, inPixels, width, height));//FIXME-interpolate() 273 int alpha = inPixels[index] & 0xff000000; 274 int rgb = ((int)(c.x * 255) << 16) | ((int)(c.y * 255) << 8) | (int)(c.z * 255); 275 outPixels[index++] = alpha | rgb; 276 } else 277 outPixels[index++] = 0; 278 } else 279 outPixels[index++] = 0; 280 } 281 } 282 return outPixels; 283 } 284 285 private int getEnvironmentMapP(Vector3f normal, int[] inPixels, int width, int height) { 286 if (environmentMap != null) { 287 float x = 0.5f * (1 + normal.x); 288 float y = 0.5f * (1 + normal.y); 289 x = ImageMath.clamp(x * envWidth, 0, envWidth-1); 290 y = ImageMath.clamp(y * envHeight, 0, envHeight-1); 291 int ix = (int)x; 292 int iy = (int)y; 293 294 float xWeight = x-ix; 295 float yWeight = y-iy; 296 int i = envWidth*iy + ix; 297 int dx = ix == envWidth-1 ? 0 : 1; 298 int dy = iy == envHeight-1 ? 0 : envWidth; 299 return ImageMath.bilinearInterpolate( xWeight, yWeight, envPixels[i], envPixels[i+dx], envPixels[i+dy], envPixels[i+dx+dy] ); 300 } 301 return 0; 302 } 303 304 public String toString() { 305 return "Stylize/Shade..."; 306 } 307 308 public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src); 309 Object o; 310 if((o=parameters.removeEL(KeyImpl.init("BumpFunction")))!=null)setBumpFunction(ImageFilterUtil.toFunction2D(o,"BumpFunction")); 311 if((o=parameters.removeEL(KeyImpl.init("BumpHeight")))!=null)setBumpHeight(ImageFilterUtil.toFloatValue(o,"BumpHeight")); 312 if((o=parameters.removeEL(KeyImpl.init("BumpSoftness")))!=null)setBumpSoftness(ImageFilterUtil.toFloatValue(o,"BumpSoftness")); 313 if((o=parameters.removeEL(KeyImpl.init("EnvironmentMap")))!=null)setEnvironmentMap(ImageFilterUtil.toBufferedImage(o,"EnvironmentMap")); 314 if((o=parameters.removeEL(KeyImpl.init("BumpSource")))!=null)setBumpSource(ImageFilterUtil.toIntValue(o,"BumpSource")); 315 316 // check for arguments not supported 317 if(parameters.size()>0) { 318 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 [BumpFunction, BumpHeight, BumpSoftness, EnvironmentMap, BumpSource]"); 319 } 320 321 return filter(src, dst); 322 } 323 }