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