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