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    
020    import railo.runtime.engine.ThreadLocalPageContext;
021    import railo.runtime.exp.FunctionException;
022    import railo.runtime.exp.PageException;
023    import railo.runtime.img.ImageUtil;
024    import railo.runtime.type.KeyImpl;
025    import railo.runtime.type.Struct;
026    import railo.runtime.type.util.CollectionUtil;
027    
028    /**
029     * A class to emboss an image.
030     */
031    public class EmbossFilter extends WholeImageFilter  implements DynFiltering {
032    
033            private final static float pixelScale = 255.9f;
034    
035            private float azimuth = 135.0f * ImageMath.PI / 180.0f, elevation = 30.0f * ImageMath.PI / 180f;
036            private boolean emboss = false;
037            private float width45 = 3.0f;
038    
039            public EmbossFilter() {
040            }
041    
042            public void setAzimuth(float azimuth) {
043                    this.azimuth = azimuth;
044            }
045            
046            public float getAzimuth() {
047                    return azimuth;
048            }
049            
050            public void setElevation(float elevation) {
051                    this.elevation = elevation;
052            }
053            
054            public float getElevation() {
055                    return elevation;
056            }
057            
058            public void setBumpHeight(float bumpHeight) {
059                    this.width45 = 3 * bumpHeight;
060            }
061    
062            public float getBumpHeight() {
063                    return width45 / 3;
064            }
065    
066            public void setEmboss(boolean emboss) {
067                    this.emboss = emboss;
068            }
069            
070            public boolean getEmboss() {
071                    return emboss;
072            }
073            
074            protected int[] filterPixels( int width, int height, int[] inPixels, Rectangle transformedSpace ) {
075                    int index = 0;
076                    int[] outPixels = new int[width * height];
077    
078                    int[] bumpPixels;
079                    int bumpMapWidth, bumpMapHeight;
080                    
081                    bumpMapWidth = width;
082                    bumpMapHeight = height;
083                    bumpPixels = new int[bumpMapWidth * bumpMapHeight];
084                    for (int i = 0; i < inPixels.length; i++)
085                            bumpPixels[i] = PixelUtils.brightness(inPixels[i]);
086    
087                    int Nx, Ny, Nz, Lx, Ly, Lz, Nz2, NzLz, NdotL;
088                    int shade, background;
089    
090                    Lx = (int)(Math.cos(azimuth) * Math.cos(elevation) * pixelScale);
091                    Ly = (int)(Math.sin(azimuth) * Math.cos(elevation) * pixelScale);
092                    Lz = (int)(Math.sin(elevation) * pixelScale);
093    
094                    Nz = (int)(6 * 255 / width45);
095                    Nz2 = Nz * Nz;
096                    NzLz = Nz * Lz;
097    
098                    background = Lz;
099    
100                    int bumpIndex = 0;
101                    
102                    for (int y = 0; y < height; y++, bumpIndex += bumpMapWidth) {
103                            int s1 = bumpIndex;
104                            int s2 = s1 + bumpMapWidth;
105                            int s3 = s2 + bumpMapWidth;
106    
107                            for (int x = 0; x < width; x++, s1++, s2++, s3++) {
108                                    if (y != 0 && y < height-2 && x != 0 && x < width-2) {
109                                            Nx = bumpPixels[s1-1] + bumpPixels[s2-1] + bumpPixels[s3-1] - bumpPixels[s1+1] - bumpPixels[s2+1] - bumpPixels[s3+1];
110                                            Ny = bumpPixels[s3-1] + bumpPixels[s3] + bumpPixels[s3+1] - bumpPixels[s1-1] - bumpPixels[s1] - bumpPixels[s1+1];
111    
112                                            if (Nx == 0 && Ny == 0)
113                                                    shade = background;
114                                            else if ((NdotL = Nx*Lx + Ny*Ly + NzLz) < 0)
115                                                    shade = 0;
116                                            else
117                                                    shade = (int)(NdotL / Math.sqrt(Nx*Nx + Ny*Ny + Nz2));
118                                    } else
119                                            shade = background;
120    
121                                    if (emboss) {
122                                            int rgb = inPixels[index];
123                                            int a = rgb & 0xff000000;
124                                            int r = (rgb >> 16) & 0xff;
125                                            int g = (rgb >> 8) & 0xff;
126                                            int b = rgb & 0xff;
127                                            r = (r*shade) >> 8;
128                                            g = (g*shade) >> 8;
129                                            b = (b*shade) >> 8;
130                                            outPixels[index++] = a | (r << 16) | (g << 8) | b;
131                                    } else
132                                            outPixels[index++] = 0xff000000 | (shade << 16) | (shade << 8) | shade;
133                            }
134                    }
135    
136                    return outPixels;
137            }
138    
139            public String toString() {
140                    return "Stylize/Emboss...";
141            }
142    
143            public BufferedImage filter(BufferedImage src, Struct parameters) throws PageException {BufferedImage dst=ImageUtil.createBufferedImage(src);
144                    Object o;
145                    if((o=parameters.removeEL(KeyImpl.init("BumpHeight")))!=null)setBumpHeight(ImageFilterUtil.toFloatValue(o,"BumpHeight"));
146                    if((o=parameters.removeEL(KeyImpl.init("Azimuth")))!=null)setAzimuth(ImageFilterUtil.toFloatValue(o,"Azimuth"));
147                    if((o=parameters.removeEL(KeyImpl.init("Elevation")))!=null)setElevation(ImageFilterUtil.toFloatValue(o,"Elevation"));
148                    if((o=parameters.removeEL(KeyImpl.init("Emboss")))!=null)setEmboss(ImageFilterUtil.toBooleanValue(o,"Emboss"));
149    
150                    // check for arguments not supported
151                    if(parameters.size()>0) {
152                            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 [BumpHeight, Azimuth, Elevation, Emboss]");
153                    }
154    
155                    return filter(src, dst);
156            }
157    }