001/* 002Copyright 2006 Jerry Huxtable 003 004Licensed under the Apache License, Version 2.0 (the "License"); 005you may not use this file except in compliance with the License. 006You may obtain a copy of the License at 007 008 http://www.apache.org/licenses/LICENSE-2.0 009 010Unless required by applicable law or agreed to in writing, software 011distributed under the License is distributed on an "AS IS" BASIS, 012WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013See the License for the specific language governing permissions and 014limitations under the License. 015*/ 016 017package com.jhlabs.image; 018 019import java.awt.image.*; 020import com.jhlabs.math.*; 021import com.jhlabs.vecmath.*; 022import java.awt.*; 023import java.io.*; 024import java.util.*; 025 026public class ShadeFilter extends WholeImageFilter { 027 028 public final static int COLORS_FROM_IMAGE = 0; 029 public final static int COLORS_CONSTANT = 1; 030 031 public final static int BUMPS_FROM_IMAGE = 0; 032 public final static int BUMPS_FROM_IMAGE_ALPHA = 1; 033 public final static int BUMPS_FROM_MAP = 2; 034 public final static int BUMPS_FROM_BEVEL = 3; 035 036 private float bumpHeight; 037 private float bumpSoftness; 038 private float viewDistance = 10000.0f; 039 private int colorSource = COLORS_FROM_IMAGE; 040 private int bumpSource = BUMPS_FROM_IMAGE; 041 private Function2D bumpFunction; 042 private BufferedImage environmentMap; 043 private int[] envPixels; 044 private int envWidth = 1, envHeight = 1; 045 private Vector3f l; 046 private Vector3f v; 047 private Vector3f n; 048 private Color4f shadedColor; 049 private Color4f diffuse_color; 050 private Color4f specular_color; 051 private Vector3f tmpv, tmpv2; 052 053 public ShadeFilter() { 054 bumpHeight = 1.0f; 055 bumpSoftness = 5.0f; 056 l = new Vector3f(); 057 v = new Vector3f(); 058 n = new Vector3f(); 059 shadedColor = new Color4f(); 060 diffuse_color = new Color4f(); 061 specular_color = new Color4f(); 062 tmpv = new Vector3f(); 063 tmpv2 = new Vector3f(); 064 } 065 066 public void setBumpFunction(Function2D bumpFunction) { 067 this.bumpFunction = bumpFunction; 068 } 069 070 public Function2D getBumpFunction() { 071 return bumpFunction; 072 } 073 074 public void setBumpHeight(float bumpHeight) { 075 this.bumpHeight = bumpHeight; 076 } 077 078 public float getBumpHeight() { 079 return bumpHeight; 080 } 081 082 public void setBumpSoftness(float bumpSoftness) { 083 this.bumpSoftness = bumpSoftness; 084 } 085 086 public float getBumpSoftness() { 087 return bumpSoftness; 088 } 089 090 public void setEnvironmentMap(BufferedImage environmentMap) { 091 this.environmentMap = environmentMap; 092 if (environmentMap != null) { 093 envWidth = environmentMap.getWidth(); 094 envHeight = environmentMap.getHeight(); 095 envPixels = getRGB( environmentMap, 0, 0, envWidth, envHeight, null ); 096 } else { 097 envWidth = envHeight = 1; 098 envPixels = null; 099 } 100 } 101 102 public BufferedImage getEnvironmentMap() { 103 return environmentMap; 104 } 105 106 public void setBumpSource(int bumpSource) { 107 this.bumpSource = bumpSource; 108 } 109 110 public int getBumpSource() { 111 return bumpSource; 112 } 113 114 protected final static float r255 = 1.0f/255.0f; 115 116 protected void setFromRGB( Color4f c, int argb ) { 117 c.set( ((argb >> 16) & 0xff) * r255, ((argb >> 8) & 0xff) * r255, (argb & 0xff) * r255, ((argb >> 24) & 0xff) * r255 ); 118 } 119 120 protected int[] filterPixels( int width, int height, int[] inPixels, Rectangle transformedSpace ) { 121 int index = 0; 122 int[] outPixels = new int[width * height]; 123 float width45 = Math.abs(6.0f * bumpHeight); 124 boolean invertBumps = bumpHeight < 0; 125 Vector3f position = new Vector3f(0.0f, 0.0f, 0.0f); 126 Vector3f viewpoint = new Vector3f((float)width / 2.0f, (float)height / 2.0f, viewDistance); 127 Vector3f normal = new Vector3f(); 128 Color4f c = new Color4f(); 129 Function2D bump = bumpFunction; 130 131 if (bumpSource == BUMPS_FROM_IMAGE || bumpSource == BUMPS_FROM_IMAGE_ALPHA || bumpSource == BUMPS_FROM_MAP || bump == null) { 132 if ( bumpSoftness != 0 ) { 133 int bumpWidth = width; 134 int bumpHeight = height; 135 int[] bumpPixels = inPixels; 136 if ( bumpSource == BUMPS_FROM_MAP && bumpFunction instanceof ImageFunction2D ) { 137 ImageFunction2D if2d = (ImageFunction2D)bumpFunction; 138 bumpWidth = if2d.getWidth(); 139 bumpHeight = if2d.getHeight(); 140 bumpPixels = if2d.getPixels(); 141 } 142 Kernel kernel = GaussianFilter.makeKernel( bumpSoftness ); 143 int [] tmpPixels = new int[bumpWidth * bumpHeight]; 144 int [] softPixels = new int[bumpWidth * bumpHeight]; 145 GaussianFilter.convolveAndTranspose( kernel, bumpPixels, tmpPixels, bumpWidth, bumpHeight, true, false, false, ConvolveFilter.CLAMP_EDGES); 146 GaussianFilter.convolveAndTranspose( kernel, tmpPixels, softPixels, bumpHeight, bumpWidth, true, false, false, ConvolveFilter.CLAMP_EDGES); 147 bump = new ImageFunction2D(softPixels, bumpWidth, bumpHeight, ImageFunction2D.CLAMP, bumpSource == BUMPS_FROM_IMAGE_ALPHA); 148 } else 149 bump = new ImageFunction2D(inPixels, width, height, ImageFunction2D.CLAMP, bumpSource == BUMPS_FROM_IMAGE_ALPHA); 150 } 151 152 Vector3f v1 = new Vector3f(); 153 Vector3f v2 = new Vector3f(); 154 Vector3f n = new Vector3f(); 155 156 // Loop through each source pixel 157 for (int y = 0; y < height; y++) { 158 float ny = y; 159 position.y = y; 160 for (int x = 0; x < width; x++) { 161 float nx = x; 162 163 // Calculate the normal at this point 164 if (bumpSource != BUMPS_FROM_BEVEL) { 165 // Complicated and slower method 166 // Calculate four normals using the gradients in +/- X/Y directions 167 int count = 0; 168 normal.x = normal.y = normal.z = 0; 169 float m0 = width45*bump.evaluate(nx, ny); 170 float m1 = x > 0 ? width45*bump.evaluate(nx - 1.0f, ny)-m0 : -2; 171 float m2 = y > 0 ? width45*bump.evaluate(nx, ny - 1.0f)-m0 : -2; 172 float m3 = x < width-1 ? width45*bump.evaluate(nx + 1.0f, ny)-m0 : -2; 173 float m4 = y < height-1 ? width45*bump.evaluate(nx, ny + 1.0f)-m0 : -2; 174 175 if (m1 != -2 && m4 != -2) { 176 v1.x = -1.0f; v1.y = 0.0f; v1.z = m1; 177 v2.x = 0.0f; v2.y = 1.0f; v2.z = m4; 178 n.cross(v1, v2); 179 n.normalize(); 180 if (n.z < 0.0) 181 n.z = -n.z; 182 normal.add(n); 183 count++; 184 } 185 186 if (m1 != -2 && m2 != -2) { 187 v1.x = -1.0f; v1.y = 0.0f; v1.z = m1; 188 v2.x = 0.0f; v2.y = -1.0f; v2.z = m2; 189 n.cross(v1, v2); 190 n.normalize(); 191 if (n.z < 0.0) 192 n.z = -n.z; 193 normal.add(n); 194 count++; 195 } 196 197 if (m2 != -2 && m3 != -2) { 198 v1.x = 0.0f; v1.y = -1.0f; v1.z = m2; 199 v2.x = 1.0f; v2.y = 0.0f; v2.z = m3; 200 n.cross(v1, v2); 201 n.normalize(); 202 if (n.z < 0.0) 203 n.z = -n.z; 204 normal.add(n); 205 count++; 206 } 207 208 if (m3 != -2 && m4 != -2) { 209 v1.x = 1.0f; v1.y = 0.0f; v1.z = m3; 210 v2.x = 0.0f; v2.y = 1.0f; v2.z = m4; 211 n.cross(v1, v2); 212 n.normalize(); 213 if (n.z < 0.0) 214 n.z = -n.z; 215 normal.add(n); 216 count++; 217 } 218 219 // Average the four normals 220 normal.x /= count; 221 normal.y /= count; 222 normal.z /= count; 223 } 224 225/* For testing - generate a sphere bump map 226 double dx = x-120; 227 double dy = y-80; 228 double r2 = dx*dx+dy*dy; 229// double r = Math.sqrt( r2 ); 230// double t = Math.atan2( dy, dx ); 231 if ( r2 < 80*80 ) { 232 double z = Math.sqrt( 80*80 - r2 ); 233 normal.x = (float)dx; 234 normal.y = (float)dy; 235 normal.z = (float)z; 236 normal.normalize(); 237 } else { 238 normal.x = 0; 239 normal.y = 0; 240 normal.z = 1; 241 } 242*/ 243 244 if (invertBumps) { 245 normal.x = -normal.x; 246 normal.y = -normal.y; 247 } 248 position.x = x; 249 250 if (normal.z >= 0) { 251 // Get the material colour at this point 252 if (environmentMap != null) { 253 //FIXME-too much normalizing going on here 254 tmpv2.set(viewpoint); 255 tmpv2.sub(position); 256 tmpv2.normalize(); 257 tmpv.set(normal); 258 tmpv.normalize(); 259 260 // Reflect 261 tmpv.scale( 2.0f*tmpv.dot(tmpv2) ); 262 tmpv.sub(v); 263 264 tmpv.normalize(); 265 setFromRGB(c, getEnvironmentMapP(normal, inPixels, width, height));//FIXME-interpolate() 266 int alpha = inPixels[index] & 0xff000000; 267 int rgb = ((int)(c.x * 255) << 16) | ((int)(c.y * 255) << 8) | (int)(c.z * 255); 268 outPixels[index++] = alpha | rgb; 269 } else 270 outPixels[index++] = 0; 271 } else 272 outPixels[index++] = 0; 273 } 274 } 275 return outPixels; 276 } 277 278 private int getEnvironmentMapP(Vector3f normal, int[] inPixels, int width, int height) { 279 if (environmentMap != null) { 280 float x = 0.5f * (1 + normal.x); 281 float y = 0.5f * (1 + normal.y); 282 x = ImageMath.clamp(x * envWidth, 0, envWidth-1); 283 y = ImageMath.clamp(y * envHeight, 0, envHeight-1); 284 int ix = (int)x; 285 int iy = (int)y; 286 287 float xWeight = x-ix; 288 float yWeight = y-iy; 289 int i = envWidth*iy + ix; 290 int dx = ix == envWidth-1 ? 0 : 1; 291 int dy = iy == envHeight-1 ? 0 : envWidth; 292 return ImageMath.bilinearInterpolate( xWeight, yWeight, envPixels[i], envPixels[i+dx], envPixels[i+dy], envPixels[i+dx+dy] ); 293 } 294 return 0; 295 } 296 297 public String toString() { 298 return "Stylize/Shade..."; 299 } 300 301}