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}